线段树分治-学习笔记
阅前须知:本文给出了线段树分治的一道例题以及多道习题,同时给出了部分实现的代码,帮助学习线段树分治。
总述
线段树分治是一种离线算法,在于把修改挂在线段树的节点上,通过遍历线段树求出每个叶子节点的答案,以减小复杂度。
例题 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);
}