线段树分治-学习笔记
阅前须知:本文给出了线段树分治的一道例题以及多道习题,同时给出了部分实现的代码,帮助学习线段树分治。
总述
线段树分治是一种离线算法,在于把修改挂在线段树的节点上,通过遍历线段树求出每个叶子节点的答案,以减小复杂度。
例题 P5787 二分图
题目大意:\(n\) 个点的图上,有 \(m\) 条边,第 \(i\) 条边 \((x,y)\) 在时刻 \(l_i\sim r_i\) 存在,问每个时刻的图是否是二分图。
解题思路:首先如果是动态加边,没有删边,怎么判断一个图是否是二分图?
可以考虑种类并查集,一共两类,一条边连接不同种类,如果连边时发现两个点属于同一种类,那么就不是二分图。这样我们就能做到 \(O(mk\alpha(n))\)。
考虑分治思想,我们把不同的询问取出它们的共同的修改,这样就能减小复杂度。
考虑把时刻 \(l_i\sim r_i\) 的修改挂在线段树上的 \(\log k\) 个区间上,这样一共有 \(m\log k\) 个修改,是可接受的。接下来我们从树根开始走,走到一个点时把修改加上,离开这个子树时把修改撤回,走到叶子时就可以记录答案了。
我们可以使用启发式合并的并查集,这样在 getfa
时就不用路径压缩,然后合并时用 vector
记录 fa
和 sz
的每个修改的位置与原来的值,在回溯时把这些修改撤销即可。
时间复杂度 \(O(k\log k\log n)\)。
参考实现:
int fa[N],sz[N];
int getfa(int x) {
if(fa[x]==x) return x;
return getfa(fa[x]);
}
#define mid ((l+r)>>1)
#define ls (x<<1)
#define rs (ls|1)
vp up[N];
void add(int x,int l,int r,int L,int R,int a,int b) {
if(R<l||r<L)return;
if(L<=l&&r<=R) {
up[x].pb({a,b});
return;
}
add(ls,l,mid,L,R,a,b),add(rs,mid+1,r,L,R,a,b);
}
void solve(int x,int l,int r,int flag) {
vp o1,o2;
if(!flag)for(auto [a,b]:up[x]) {
int u=getfa(a),v=getfa(b);
if(u==v) flag=1;
u=getfa(a),v=getfa(b+n);
if(u!=v) {
if(sz[u]<sz[v]) swap(u,v);
o1.pb({u,sz[u]}),o2.pb({v,fa[v]});
sz[u]+=sz[v],fa[v]=u;
}
u=getfa(a+n),v=getfa(b);
if(u!=v) {
if(sz[u]<sz[v]) swap(u,v);
o1.pb({u,sz[u]}),o2.pb({v,fa[v]});
sz[u]+=sz[v],fa[v]=u;
}
}
if(l==r) {
if(flag) write("No\n");
else write("Yes\n");
for(auto [a,b]:o1) sz[a]=b;
for(auto [a,b]:o2) fa[a]=b;
return;
}
solve(ls,l,mid,flag);
solve(rs,mid+1,r,flag);
reverse(o1.begin(),o1.end());
reverse(o2.begin(),o2.end());
for(auto [a,b]:o1) sz[a]=b;
for(auto [a,b]:o2) fa[a]=b;
}
signed main(){
read(n,m,K);
fo(i,1,n*2) fa[i]=i,sz[i]=1;
fo(i,1,m) {
int l,r,x,y;
read(x,y,l,r);
++l,++r;
if(l==r) continue;
add(1,1,K,l,r-1,x,y);
}
solve(1,1,K,0);
return 0;
}
练习A AHOI2013 连通图
题目大意:每次询问断掉若干条边,问图是否还连通。
解题思路:我们把每条边断开的位置找出来,这样就分成了若干个区间,每个区间都表示这条边连上。
考虑线段树分治,把区间挂在线段树上。那么考虑怎么判断是否连通,用并查集缩点,如果成功缩点连通块个数减一,判断连通块个数是否为 1 即可。
参考实现:
const int N=1e6+5;
int X[N],Y[N];
vi d[N];
int fa[N],sz[N];
int getfa(int x) {
if(fa[x]==x)return x;
return getfa(fa[x]);
}
int n,m,K;
#define ls (x<<1)
#define rs (ls|1)
#define mid ((l+r)>>1)
vp c[N];
void add(int x,int l,int r,int L,int R,int a,int b) {
if(R<l||r<L) return;
if(L<=l&&r<=R) {
c[x].pb({a,b});
return;
}
add(ls,l,mid,L,R,a,b),add(rs,mid+1,r,L,R,a,b);
}
void solve(int x,int l,int r,int s) {
vp o1,o2;
for(auto [a,b]:c[x]) {
a=getfa(a),b=getfa(b);
if(a!=b) {
if(sz[a]<sz[b]) swap(a,b);
o1.pb({a,sz[a]}),o2.pb({b,fa[b]});
fa[b]=a,sz[a]+=sz[b];
--s;
}
}
if(l==r) {
if(s==1) write("Connected\n");
else write("Disconnected\n");
reverse(o1.begin(),o1.end());
reverse(o2.begin(),o2.end());
for(auto [a,b]:o1) sz[a]=b;
for(auto [a,b]:o2) fa[a]=b;
return;
}
solve(ls,l,mid,s);
solve(rs,mid+1,r,s);
reverse(o1.begin(),o1.end());
reverse(o2.begin(),o2.end());
for(auto [a,b]:o1) sz[a]=b;
for(auto [a,b]:o2) fa[a]=b;
}
signed main(){
read(n,m);
fo(i,1,m) read(X[i],Y[i]);
read(K);
fo(i,1,K) {
int t; read(t);
fo(j,1,t) {
int x; read(x);
d[x].pb(i);
}
}
fo(i,1,m) {
int lst=0;
for(auto j:d[i]) {
if(lst+1<j) add(1,1,K,lst+1,j-1,X[i],Y[i]);
lst=j;
}
if(lst<K) add(1,1,K,lst+1,K,X[i],Y[i]);
}
fo(i,1,n) sz[i]=1,fa[i]=i;
solve(1,1,K,n);
return 0;
}
练习B CF813F
记录每一种边上一次出现的位置即可转化为例题。
参考实现:
const int N=1e6+5;
int fa[N],sz[N];
int getfa(int x) {
if(fa[x]==x) return x;
return getfa(fa[x]);
}
int tot;
map<pi,int> mp;
int X[N],Y[N];
int lst[N],opt[N];
vp d[N];
#define ls (x<<1)
#define rs (ls|1)
#define mid ((l+r)>>1)
void add(int x,int l,int r,int L,int R,int a,int b) {
if(L<=l&&r<=R) {
d[x].pb({a,b});
return;
}
if(R<l||r<L) return;
add(ls,l,mid,L,R,a,b),add(rs,mid+1,r,L,R,a,b);
}
void solve(int x,int l,int r,int flag){
vp o1,o2;
for(auto [a,b]:d[x]) {
int u=getfa(a),v=getfa(b);
if(u==v) flag=1;
v=getfa(b+n);
if(u!=v) {
if(sz[u]<sz[v]) swap(u,v);
o1.pb({u,sz[u]}),o2.pb({v,fa[v]});
sz[u]+=sz[v],fa[v]=u;
}
u=getfa(a+n),v=getfa(b);
if(u!=v) {
if(sz[u]<sz[v]) swap(u,v);
o1.pb({u,sz[u]}),o2.pb({v,fa[v]});
sz[u]+=sz[v],fa[v]=u;
}
}
if(l==r) {
if(flag) write("NO\n");
else write("YES\n");
reverse(o1.begin(),o1.end());
reverse(o2.begin(),o2.end());
for(auto [a,b]:o1) sz[a]=b;
for(auto [a,b]:o2) fa[a]=b;
return;
}
solve(ls,l,mid,flag);
solve(rs,mid+1,r,flag);
reverse(o1.begin(),o1.end());
reverse(o2.begin(),o2.end());
for(auto [a,b]:o1) sz[a]=b;
for(auto [a,b]:o2) fa[a]=b;
}
signed main(){
read(n,Q);
fo(i,1,2*n) fa[i]=i,sz[i]=1;
fo(i,1,Q) {
int x,y; read(x,y);
if(mp.find({x,y})==mp.end())mp[{x,y}]=++tot,X[tot]=x,Y[tot]=y;
int t=mp[{x,y}];
opt[t]^=1;
if(opt[t]==0) {
add(1,1,Q,lst[t],i-1,x,y);
}
lst[t]=i;
}
fo(i,1,tot) if(opt[i]) add(1,1,Q,lst[i],Q,X[i],Y[i]);
solve(1,1,Q,0);
}
标签:return,int,线段,分治,笔记,fa,getfa
From: https://www.cnblogs.com/dccy/p/18664779