线段树分治-学习笔记
线段树分治-学习笔记
阅前须知:本文给出了线段树分治的一道例题以及多道习题,同时给出了部分实现的代码,帮助学习线段树分治。
总述
线段树分治是一种离线算法,在于把修改挂在线段树的节点上,通过遍历线段树求出每个叶子节点的答案,以减小复杂度。
例题 P5787 二分图
题目大意:
解题思路:首先如果是动态加边,没有删边,怎么判断一个图是否是二分图?
可以考虑种类并查集,一共两类,一条边连接不同种类,如果连边时发现两个点属于同一种类,那么就不是二分图。这样我们就能做到
考虑分治思想,我们把不同的询问取出它们的共同的修改,这样就能减小复杂度。
考虑把时刻
我们可以使用启发式合并的并查集,这样在 getfa
时就不用路径压缩,然后合并时用 vector
记录 fa
和 sz
的每个修改的位置与原来的值,在回溯时把这些修改撤销即可。
时间复杂度
参考实现:
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); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下