Libre OJ 2255 (线段树优化建图+Tarjan缩点+DP)
题面
分析
主体思路:若x能引爆y,从x向y连一条有向边,最后的答案就是从x出发能够到达的点的个数
首先我们发现一个炸弹可以波及到的范围一定是坐标轴上的一段连续区间
我们可以用二分查找求出炸弹能波及到最左边和最右边的点,记为[l,r]
然后我们就需要向编号属于区间[l,r]的点连一条有向边
如果直接连边,时间复杂度为\(O(n^2)\) 无法接受,考虑用线段树优化连边
我们将线段树看成一个有向图,每个线段树节点看成图上的一个点,[l,r]向[l,mid],[mid+1,r]连边,叶子节点[l,l]向原图上的节点l连边
对于从x向编号属于区间[L,R]的点连边,我们用类似线段树区间更新的方法,将[L,R]拆成许多个小区间,再直接向这些小区间暴力连边
根据线段树的性质,最多会分出\(\left[ \log _{2}n\right]\)个节点,所以单次连边的时间复杂度为\(O(\log n)\)
然后就很套路了,显然环上的点可以缩成一个大点(权值为环上所有节点权值之和(线段树节点权值为0,原图上节点权值为1))
Tarjan完在DAG上DP即可
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<stack>
#include<queue>
#include<vector>
#define maxn 1700005
#define mod 1000000007
using namespace std;
inline void qread(int &x) {
x=0;
int sign=1;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') sign=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
x=x*sign;
}
inline void qread(long long &x) {
x=0;
long long sign=1;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') sign=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
x=x*sign;
}
int n;
long long x[maxn];
long long r[maxn];
struct edge {
int from;
int to;
edge() {
}
edge(int u,int v) {
from=u;
to=v;
}
friend bool operator == (edge a,edge b) {
return a.to==b.to&&a.from==b.from;
}
friend bool operator < (edge a,edge b) {
if(a.from==b.from) return a.to<b.to;
else return a.from<b.from;
}
};
set<edge>vis1;
set<edge>vis2;
vector<int>G[maxn],D[maxn];
int w[maxn];
void add_edge(int u,int v) {
G[u].push_back(v);
}
int newn=n;
struct node {
int l;
int r;
} tree[maxn];
void build(int l,int r,int pos) {
newn++;
tree[pos].l=l;
tree[pos].r=r;
if(l==r) {
add_edge(pos+n,l);
return;
}
add_edge(pos+n,pos*2+n);
add_edge(pos+n,pos*2+1+n);
int mid=(l+r)>>1;
build(l,mid,pos<<1);
build(mid+1,r,pos<<1|1);
}
void update(int L,int R,int v,int pos) {
if(L<=tree[pos].l&&R>=tree[pos].r) {
add_edge(v,pos+n);
return;
}
int mid=(tree[pos].l+tree[pos].r)>>1;
if(L<=mid) update(L,R,v,pos<<1);
if(R>mid) update(L,R,v,pos<<1|1);
}
stack<int>s;
int tim=0;
int m=0;
int ins[maxn];
int dfn[maxn];
int low[maxn];
int belong[maxn];
int sz[maxn];
void tarjan(int x) {
s.push(x);
ins[x]=1;
dfn[x]=low[x]=++tim;
int tmp=G[x].size();
for(int i=0; i<tmp; i++) {
int y=G[x][i];
if(!dfn[y]) {
tarjan(y);
low[x]=min(low[x],low[y]);
} else if(ins[y]) {
low[x]=min(low[x],dfn[y]);
}
}
if(low[x]==dfn[x]) {
m++;
int y;
do {
y=s.top();
s.pop();
ins[y]=0;
belong[y]=m;
sz[m]+=w[y];
} while(x!=y);
}
}
void dcg_to_dag() {
for(int i=1; i<=n; i++) {
if(!dfn[i]) tarjan(i);
}
int s;
for(int i=1; i<=n; i++) {
s=G[i].size();
for(int j=0; j<s; j++) {
if(belong[i]!=belong[G[i][j]]&&!vis2.count(edge(belong[i],belong[G[i][j]]))) {
vis2.insert(edge(belong[i],belong[G[i][j]]));
D[belong[i]].push_back(belong[G[i][j]]);
}
}
}
}
long long dp[maxn];
int dfs(int x){
if(dp[x]) return dp[x];
dp[x]=sz[x];
int tmp=D[x].size();
for(int i=0;i<tmp;i++){
int y=D[x][i];
dp[x]+=dfs(y);
}
return dp[x];
}
long long solve() {
long long ans=0;
for(int i=1;i<=n;i++){
if(!dp[i]) dfs(i);
}
for(int i=1; i<=n; i++) {
ans=(ans+w[i]*i*dp[belong[i]]%mod)%mod;
}
return ans;
}
int main() {
int L,R;
qread(n);
for(int i=1; i<=n; i++) {
w[i]=1;
qread(x[i]);
qread(r[i]);
}
newn=n;
build(1,n,1);
for(int i=1; i<=n; i++) {
L=lower_bound(x+1,x+1+n,x[i]-r[i])-x;
R=upper_bound(x+1,x+1+n,x[i]+r[i])-x-1;
update(L,R,i,1);
}
n=newn;
dcg_to_dag();
printf("%lld\n",solve());
}
Libre OJ 2255 (线段树优化建图+Tarjan缩点+DP)的更多相关文章
- 【2019.7.26 NOIP模拟赛 T3】化学反应(reaction)(线段树优化建图+Tarjan缩点+拓扑排序)
题意转化 考虑我们对于每一对激活关系建一条有向边,则对于每一个点,其答案就是其所能到达的点数. 于是,这个问题就被我们搬到了图上,成了一个图论题. 优化建图 考虑我们每次需要将一个区间向一个区间连边. ...
- bzoj5017 [Snoi2017]炸弹 (线段树优化建图+)tarjan 缩点+拓扑排序
题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=5017 题解 这个题目方法挺多的. 线段树优化建图 线段树优化建图的做法应该挺显然的,一个炸弹能 ...
- bzoj5017 炸弹 (线段树优化建图+tarjan+拓扑序dp)
直接建图边数太多,用线段树优化一下 然后缩点,记下来每个点里有多少个炸弹 然后按拓扑序反向dp一下就行了 #include<bits/stdc++.h> #define pa pair&l ...
- 【bzoj5017】[Snoi2017]炸弹 线段树优化建图+Tarjan+拓扑排序
题目描述 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足: Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被引爆. 现在 ...
- 炸弹:线段树优化建边+tarjan缩点+建反边+跑拓扑
这道题我做了有半个月了...终于A了... 有图为证 一句话题解:二分LR线段树优化建边+tarjan缩点+建反边+跑拓扑统计答案 首先我们根据题意,判断出来要炸弹可以连着炸,就是这个炸弹能炸到的可以 ...
- BZOJ5017 [SNOI2017]炸弹 - 线段树优化建图+Tarjan
Solution 一个点向一个区间内的所有点连边, 可以用线段树优化建图来优化 : 前置技能传送门 然后就得到一个有向图, 一个联通块内的炸弹可以互相引爆, 所以进行缩点变成$DAG$ 然后拓扑排序. ...
- 『炸弹 线段树优化建图 Tarjan』
炸弹(SNOI2017) Description 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸 时,如果另一个炸弹所在位置 Xj 满足: Xi−Ri≤Xj≤Xi ...
- 【2019北京集训2】duck 线段树优化建图+tarjan
题目大意:给你$n$个点,第$i$个点有点权$v_i$.你需要将这$n$个点排成一排,第$i$个点的点权能被累加当且仅当这个点前面存在编号在$[l_i,r_i]$中的点,问你这些点应该如何排列,点权和 ...
- BZOJ5017 炸弹(线段树优化建图+Tarjan+拓扑)
Description 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足: Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被 ...
随机推荐
- Java中的集合详解及代码测试
1:对象数组 (1)数组既可以存储基本数据类型,也可以存储引用类型.它存储引用类型的时候的数组就叫对象数组. 2:集合(Collection) (1)集合的由来 我们学习的是Java -- 面向对象 ...
- 2019-9-2-visual-studio-2015-warning-MSB3246
title author date CreateTime categories visual studio 2015 warning MSB3246 lindexi 2019-09-02 12:57: ...
- R语言封装函数
R语言封装函数 原帖见豆瓣:https://www.douban.com/note/279077707/ 一个完整的R函数,需要包括函数名称,函数声明,函数参数以及函数体几部分. 1. 函数名称,即要 ...
- Linux性能优化从入门到实战:16 文件系统篇:总结磁盘I/O指标/工具、问题定位和调优
(1)磁盘 I/O 性能指标 文件系统和磁盘 I/O 指标对应的工具 文件系统和磁盘 I/O 工具对应的指标 (2)磁盘 I/O 问题定位分析思路 (3)I/O 性能优化思路 Step 1:首先采用 ...
- 前端每日实战:45# 视频演示如何用纯 CSS 创作一个菱形 loader 动画
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/eKzjqK 可交互视频教程 此视频 ...
- JVM加载class文件原理
装载的概念 所谓装载就是寻找一个类或是一个接口的二进制形式并用该二进制形式来构造代表这个类或是这个接口的class对象的过程. Java中类装载器装载类到虚拟机 在Java中,类装载器把一个类装入Ja ...
- LeetCode--057--插入区间(java)
给出一个无重叠的 ,按照区间起始端点排序的区间列表. 在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间). 示例 1: 输入: intervals = [ ...
- u盘被占用,无法弹出解决办法
方法1.把鼠标放到电脑屏幕最底部的中央,点击右键,点击 任务管理器 方法2.按:CTRL+ALT+ENTER(回车) 打开任务管理器,点击 进入性能后点击下方的:资源管理器 回到桌面,查看 ...
- redis集群报错:(error) CLUSTERDOWN The cluster is down
更换了电脑,把原来的电脑上的虚拟机复制到了新电脑上,启动虚拟机上的centos系统,然后启动redis集群(redis5版本),发现集群可以启动,redis进程也有,但是连接集群中的任意节点就报错,如 ...
- C++指针的指针和指针的引用
https://www.cnblogs.com/li-peng/p/4116349.html