线段树优化建图

有一类经典的问题,之前见过很多次了,好像某一场区域赛的热身赛里就出过:一维数轴上有nn个炸弹,xi,rix_i,r_i分别表示第ii个炸弹的位置和引爆半径,即如果引爆炸弹ii,那么位置在[xiri,xi+ri][x_i-r_i,x_i+r_i]范围内的炸弹都会爆炸,并且引发连锁反应。然后比如询问最少引爆几个炸弹才能全部炸完。

首先一个建图思路是如果ii能引爆jj,那么iijj连一条边,做tarjan求强连通分量就可以知道很多信息了。但是这样暴力连边是O(n2)O(n^2)的,需要优化。一个显然的观察是,一个点ii的引爆范围是一段区间(这句是废话),所以是和一个区间的点连边,那么我们把炸弹按位置排序后,11nn编好号,建线段树,线段树每个点向左右儿子分别连边。如果ii要向区间(l,r)(l,r)所有点连边的话,就在线段树中将区间(l,r)(l,r)分解成O(logn)O(logn)条线段,这样就转化成了向对应的线段树中的一些点连边,总的边数就是O(nlogn)O(nlogn)

来看两道具体的题目:

[bzoj 5017 Snoi2017]炸弹

询问如果把第 ii 个炸弹引爆,将引爆多少个炸弹。

线段树优化建图,然后tarjan,答案是从ii所在的SCC出发,能走到的SCC含有的原炸弹数之和。因为对于每个ii都要询问,所以这里不能暴力,一个方法是在这个DAG的反图上拓扑序dp一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 2000010
#define M 11000010
const ll mod=1e9+7;
const ll mod2=mod*mod;
struct node
{
ll x,r;
} a[N];
ll pos[N];
int tot,rt,n;
int ls[N],rs[N];
int e,head[M],last[M],p[N];
int dfn[N],low[N],sta[N],color[N],val[N];
int in[N],q[N];
bool v[N];
int dfstime,num,siz,len;
vector<int> redge[N];
pair<int,int> pr[M];
bool cmp(const node &a,const node &b)
{
return a.x<b.x;
}
void add(int x,int y)
{
head[++e]=y;
last[e]=p[x];
p[x]=e;
}
void build(int &x,int l,int r)
{
x=(l==r)?l:(++tot);
if(l==r) return;
int mid=l+r>>1;
build(ls[x],l,mid);
build(rs[x],mid+1,r);
add(x,ls[x]);
add(x,rs[x]);
}
void ins(int x,int l,int r,int L,int R,int y)
{
if(L<=l && r<=R)
{
add(y,x);
return;
}
int mid=l+r>>1;
if(L<=mid) ins(ls[x],l,mid,L,R,y);
if(mid<R) ins(rs[x],mid+1,r,L,R,y);
}
void tarjan(int x)
{
int y;
dfn[x]=low[x]=++dfstime;
sta[++siz]=x;
v[x]=true;
for(int j=p[x];j;j=last[j])
if(!dfn[y=head[j]])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(v[y])
low[x]=min(low[x],dfn[y]);
if(low[x]==dfn[x])
{
++num;
do
{
y=sta[siz--];
color[y]=num;
v[y]=false;
}while(y!=x);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%lld%lld",&a[i].x,&a[i].r);
pos[i]=a[i].x;
}
tot=n;
build(rt,1,n);
for(int i=1;i<=n;++i)
{
int l=lower_bound(pos+1,pos+n+1,a[i].x-a[i].r)-pos;
int r=upper_bound(pos+1,pos+n+1,a[i].x+a[i].r)-pos-1;
ins(rt,1,n,l,r,i);
}
for(int i=1;i<=tot;++i)
if(!dfn[i])
tarjan(i);
for(int i=1;i<=n;++i)
val[color[i]]++;
for(int x=1;x<=tot;++x)
{
for(int j=p[x];j;j=last[j])
{
int y=head[j];
if(color[x]==color[y]) continue;
pr[++len]=make_pair(color[y],color[x]);
}
}
sort(pr+1,pr+len+1);
len=unique(pr+1,pr+len+1)-pr-1;
for(int i=1;i<=len;++i)
{
redge[pr[i].first].push_back(pr[i].second);
++in[pr[i].second];
}
int l=0,r=0;
for(int i=1;i<=num;++i)
if(!in[i])
q[r++]=i;
while(l!=r)
{
int x=q[l++];
for(int i=0;i<redge[x].size();++i)
{
int y=redge[x][i];
val[y]+=val[x];
if((--in[y])==0) q[r++]=y;
}
}
ll ans=0;
for(int i=1;i<=n;++i)
{
ans+=1LL*i*val[color[i]];
if(ans>=mod2) ans-=mod2;
}
ans%=mod;
printf("%d\n",ans);
}

Petrozavodsk Winter-2018. Carnegie Mellon U Contest A Mines

现在每个炸弹多了一个初始的引爆费用,因为连锁发生的爆炸是免费的。有qq次修改,每次修改一个炸弹的费用,对于每次修改都要查询当前要引爆所有炸弹的最小花费。

前面的操作都一样,首先一个显然的想法是只要引爆DAG中所有入度为00的SCC里的费用最小的炸弹。然后有个致命的问题是,入度为00的SCC可能不含有原11nn号点,所以需要做拓扑排序删掉没用的点,之后对于每次询问用set维护即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define NN 200010
#define N 800010
#define M 16000010
#define rep(i,l,r) for(int i=l;i<=r;++i)
struct node
{
int x,r,c,o;
} a[NN];
int h[NN],pos[NN],ls[N],rs[N];
int n,tot,m,rt,dfstime,siz,num;
int e,head[M],last[M],p[N];
int dfn[N],low[N],sta[N],color[N];
int in[N],q[N];
bool v[N],ok[N];
vector<int> edge[N];
multiset<int> st[N];
bool cmp(const node &a,const node &b)
{
return a.x<b.x;
}

void add(int x,int y)
{
head[++e]=y;
last[e]=p[x];
p[x]=e;
}
void tarjan(int x)
{
int y;
dfn[x]=low[x]=++dfstime;
sta[++siz]=x;
v[x]=true;
for(int j=p[x];j;j=last[j])
if(!dfn[y=head[j]])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(v[y])
low[x]=min(low[x],dfn[y]);
if(low[x]==dfn[x])
{
++num;
do
{
y=sta[siz--];
color[y]=num;
v[y]=false;
}while(y!=x);
}
}
void build(int &x,int l,int r)
{
x=(l==r)?l:(++tot);
if(l==r) return;
int mid=l+r>>1;
build(ls[x],l,mid);
build(rs[x],mid+1,r);
add(x,ls[x]);
add(x,rs[x]);
}
void ins(int x,int l,int r,int L,int R,int y)
{
if(L<=l && r<=R)
{
add(y,x);
return;
}
int mid=l+r>>1;
if(L<=mid) ins(ls[x],l,mid,L,R,y);
if(mid<R) ins(rs[x],mid+1,r,L,R,y);
}
int main()
{
#ifdef SK
freopen("input.txt","r",stdin);
#endif // SK
scanf("%d%d",&n,&m);
rep(i,1,n)
{
scanf("%d%d%d",&a[i].x,&a[i].r,&a[i].c);
a[i].o=i;
}
sort(a+1,a+n+1,cmp);
rep(i,1,n) h[i]=a[i].x,pos[a[i].o]=i;
tot=n;
build(rt,1,n);
rep(i,1,n)
{
int l=lower_bound(h+1,h+n+1,a[i].x-a[i].r)-h;
int r=upper_bound(h+1,h+n+1,a[i].x+a[i].r)-h-1;
ins(rt,1,n,l,r,i);
}
rep(i,1,tot)
if(!dfn[i])
tarjan(i);
rep(i,1,n) ok[color[i]]=true;
for(int x=1;x<=tot;++x)
{
for(int j=p[x];j;j=last[j])
{
int y=head[j];
int xx=color[x],yy=color[y];
if(xx==yy) continue;
edge[xx].push_back(yy);
++in[yy];
}
}
int l=0,r=0;
rep(i,1,num)
if(!in[i] && !ok[i])
q[r++]=i;
while(l!=r)
{
int x=q[l++];
for(auto &y:edge[x])
{
--in[y];
if(!in[y] && !ok[y]) q[r++]=y;
}
}

rep(i,1,n)
if(!in[color[i]])
st[color[i]].insert(a[i].c);

ll ans=0;
rep(i,1,num)
if(ok[i] && !in[i] && !st[i].empty())
ans+=*st[i].begin();

while(m--)
{
int x,y;
scanf("%d%d",&x,&y);
x=pos[x];
if(!in[color[x]])
{
ans-=*st[color[x]].begin();
auto it=st[color[x]].find(a[x].c);
st[color[x]].erase(it);
a[x].c=y;
st[color[x]].insert(a[x].c);
ans+=*st[color[x]].begin();
}
printf("%lld\n",ans);
}
return 0;
}

线段树优化建图
https://izard.space/2018/09/30/线段树优化建图/
作者
forever_you
发布于
2018年9月30日
许可协议