【学习笔记】动态树Link-Cut-Tree
这是两个月前写的,看能不能搬运过来……
动态树是一类维护森林连通性的问题(已纠正,感谢ZQC巨佬),目前最(wo)常(zhi)见(hui)的动态树就是LCT(Link-Cut-Tree),然而LCT似乎是处理路径的,处理子树可能力不足。据说有一种称为Top Tree的数据结构,可以处理所有。但是学不动了Orz
LCT中最主要的是Access操作,Access(u)操作的含义是,从当前的节点u向他所在的根节点连一条重路径,这是相当于把沿路的重路径全都断开,重新拉一条从u到根的重路径。
makeroot(x)操作:
若想让u成为当前树的根节点,则可以先access(u),再splay(u),把u转为当前splay的根节点。因为splay维护的是深度,所以u没有右儿子(没有比u还要深的点,因为重链定义),所以换根就相当于一次区间翻转,交换左右子树即完成区间翻转。此时就可以打标记了。
所以,makeroot=access+splay+rev
还有一个isroot操作,超级简单,就是判断这是不是一条重路径的根,只要他的fa指针指向的节点的左右子树都不是他(证明此时这是一条虚边),那么这就是一棵子树的根节点。
link(x,y)操作:
在u和v之间连边,可以makeroot(u),再让u的父亲为v,这就相当于v本身是一棵splay,而u和v之间连了条轻边。
cut(u,v)操作:
断开u和v之间的边,可以先makeroot(u),再access(v),再splay(v),此时v的左儿子必定为u,于是断开v和v的左儿子即可。
至于翻转标记的传递,就是跟Splay一样就行了。
但标记下放有一个问题。因为splay是时时刻刻在分裂与合并的,因为要动态维护每条重链,所以在splay之前,要先把根节点到当前节点全部下放一遍标记,防止标记下放不完全。
split(x,y)操作:
split相当于把两个子树分开,考虑到我们cut的时候第一步也是分开,所以
$split=makeroot(u)+access(v)+splay(v)$
然后还要保存一些轻边(虚边),对于轻边我们需要单独记录处理。在原树上,当前重链的顶端节点与他的父亲的边即为轻边,如果不记录,树将是不完整的。
具体到代码实现,可以是当前splay的根节点的父亲即为当前splay上面的那个重链所在的splay上的点,但上面的splay的儿子并不指向当前splay的父亲,即为用splay的根节点的父亲来存储轻边。
动态树的主要时间消耗在Access上,而Access的时间复杂度是
$O(nlogn)$
好像最后在UOJ群里看到一句话:
*树剖能做的,动态树都能做,只不过有的东西动态树做起来比树剖烦的多*
好像超级有道理,因为我写的维护子树size,树剖的话天生自带,但是LCT每次Access跳轻重边的时候都要交换,超级烦。
下面是这几天做的一点题目:
1.洞穴勘测
裸的连通性询问。
1 #include<bits/stdc++.h> 2 #define N 10005 3 #define inf 1000000007 4 using namespace std; 5 int n,m; 6 struct Link_Cut_Tree{ 7 int fa[N],c[N][2],q[N],top;bool rev[N]; 8 Link_Cut_Tree(){ 9 top=0;memset(fa,0,sizeof(fa)); 10 memset(c,0,sizeof(c)); 11 memset(rev,0,sizeof(rev)); 12 } 13 inline bool isroot(int x){return c[fa[x]][1]!=x&&c[fa[x]][0]!=x;} 14 inline void pushdown(int x){ 15 int l=c[x][0],r=c[x][1]; 16 if(rev[x]){ 17 rev[x]^=1;rev[l]^=1;rev[r]^=1; 18 swap(c[x][1],c[x][0]); 19 } 20 } 21 void rotate(int x){ 22 int y=fa[x],z=fa[y],l,r; 23 l=(c[y][1]==x);r=l^1; 24 if(!isroot(y))c[z][c[z][1]==y]=x; 25 fa[c[x][r]]=y;fa[y]=x;fa[x]=z; 26 c[y][l]=c[x][r];c[x][r]=y; 27 } 28 void splay(int x){ 29 int top=0;q[++top]=x; 30 for(int i=x;!isroot(i);i=fa[i])q[++top]=fa[i]; 31 while(top)pushdown(q[top--]); 32 while(!isroot(x)){ 33 int y=fa[x],z=fa[y]; 34 if(!isroot(y)){ 35 if(c[y][0]==x^c[z][0]==y)rotate(x);else rotate(y); 36 } 37 rotate(x); 38 } 39 } 40 void access(int x){for(int t=0;x;t=x,x=fa[x])splay(x),c[x][1]=t;} 41 void makeroot(int x){access(x);splay(x);rev[x]^=1;} 42 void link(int x,int y){makeroot(x);fa[x]=y;splay(x);} 43 void split(int x,int y){makeroot(x);access(y);splay(y);} 44 void cut(int x,int y){split(x,y);c[y][0]=fa[x]=0;} 45 int find(int x){ 46 access(x);splay(x);int y=x;while(c[y][0])y=c[y][0];return y; 47 } 48 }T; 49 inline int read(){ 50 int x=0,f=1;char ch; 51 do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9'); 52 do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9'); 53 return f*x; 54 } 55 int main(){ 56 char s[10];int x,y; 57 n=read();m=read(); 58 for(int i=1;i<=m;i++){ 59 scanf("%s",s); 60 x=read();y=read(); 61 if(s[0]=='C')T.link(x,y); 62 if(s[0]=='D')T.cut(x,y); 63 if(s[0]=='Q'){ 64 int xx=T.find(x),yy=T.find(y); 65 if(xx==yy)puts("Yes");else puts("No"); 66 } 67 } 68 return 0; 69 }
2.bzoj1180 OTOCI
修改,连边,区间权值合。
其实可以先读入所有的边得到最后的树,然后离线树剖,用并查集维护边就行了
本蒟蒻还是写了Link-Cut-Tree
1 #include<bits/stdc++.h> 2 #define N 30005 3 #define inf 1000000000 4 #define yql 1000000007 5 #define ll long long 6 using namespace std; 7 int q,n; 8 struct Link_Cut_Tree{ 9 int c[N][2],fa[N],sumv[N],val[N],q[N],top[N]; 10 bool rev[N]; 11 void pushup(int x){ 12 int l=c[x][0],r=c[x][1];sumv[x]=sumv[l]+sumv[r]+val[x]; 13 } 14 void pushdown(int x){ 15 int l=c[x][0],r=c[x][1]; 16 if(rev[x]){ 17 rev[x]^=1;rev[r]^=1;rev[l]^=1; 18 swap(c[x][1],c[x][0]); 19 } 20 } 21 bool isroot(int x){return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;} 22 void rotate(int x){ 23 int y=fa[x],z=fa[y],l,r; 24 l=(c[y][1]==x);r=l^1; 25 if(!isroot(y))c[z][c[z][1]==y]=x; 26 fa[c[x][r]]=y;fa[y]=x;fa[x]=z; 27 c[y][l]=c[x][r];c[x][r]=y; 28 pushup(y);pushup(x); 29 } 30 void splay(int x){ 31 int top=0;q[++top]=x; 32 for(int i=x;!isroot(i);i=fa[i])q[++top]=fa[i]; 33 while(top)pushdown(q[top--]); 34 while(!isroot(x)){ 35 int y=fa[x],z=fa[y]; 36 if(!isroot(y)){ 37 if(c[y][0]==x^c[z][0]==y)rotate(x);else rotate(y); 38 } 39 rotate(x); 40 } 41 } 42 void access(int x){for(int t=0;x;t=x,x=fa[x])splay(x),c[x][1]=t,pushup(x);} 43 void makeroot(int x){access(x);splay(x);rev[x]^=1;} 44 void link(int x,int y){makeroot(x);fa[x]=y;} 45 void split(int x,int y){makeroot(x);access(y);splay(y);} 46 void cut(int x,int y){split(x,y);c[y][0]=fa[x]=0;} 47 int find(int x){ 48 access(x);splay(x);int y=x;while(c[y][0])y=c[y][0];return y; 49 } 50 }T; 51 inline int read(){ 52 int f=1,x=0;char ch; 53 do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9'); 54 do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9'); 55 return f*x; 56 } 57 int main(){ 58 n=read(); 59 for(int i=1;i<=n;i++){T.sumv[i]=read();T.val[i]=T.sumv[i];} 60 q=read();char s[20]; 61 while(q--){ 62 scanf("%s",s);int x=read(),y=read(); 63 if(s[0]=='b'){ 64 int xx=T.find(x),yy=T.find(y); 65 if(xx==yy)puts("no"); 66 else puts("yes"),T.link(x,y); 67 } 68 if(s[0]=='p')T.makeroot(x),T.val[x]=y,T.pushup(x); 69 if(s[0]=='e'){ 70 int xx=T.find(x),yy=T.find(y); 71 if(xx!=yy)puts("impossible"); 72 else{ 73 T.makeroot(x);T.access(y);T.splay(y); 74 printf("%d\n",T.sumv[y]); 75 } 76 } 77 } 78 return 0; 79 }
3.NOI2014 膜法森林
考虑Kruskal求出最小生成树,每次加,如果当前的边成环,那么就在之前的边里找一个最大权的cut掉。
维护连通性,cut边,最大值询问,LCT很擅长啦~
1 #include<bits/stdc++.h> 2 #define inf 1000000007 3 #define N 200005 4 using namespace std; 5 int n,m,cnt,_=inf; 6 int f[N]; 7 int find(int x){return x==f[x]?x:f[x]=find(f[x]);} 8 struct Edge{ 9 int u,v,w,c; 10 bool operator<(const Edge &x)const{return w<x.w;} 11 }G[2*N]; 12 13 struct Link_Cut_Tree{ 14 int c[N][2],fa[N],q[N],rev[N],top; 15 int maxv[N],val[N]; 16 inline void pushup(int x){ 17 int l=c[x][0],r=c[x][1]; 18 maxv[x]=x; 19 if(val[maxv[l]]>val[maxv[x]])maxv[x]=maxv[l]; 20 if(val[maxv[r]]>val[maxv[x]])maxv[x]=maxv[r]; 21 } 22 Link_Cut_Tree(){top=0;memset(rev,0,sizeof(rev));} 23 bool isroot(int x){return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;} 24 void pushdown(int x){ 25 int l=c[x][0],r=c[x][1]; 26 if(rev[x]){ 27 rev[l]^=1;rev[r]^=1;rev[x]^=1; 28 swap(c[x][1],c[x][0]); 29 } 30 } 31 void rotate(int x){ 32 int y=fa[x],z=fa[y],l,r; 33 l=(c[y][1])==x;r=l^1; 34 if(!isroot(y))c[z][c[z][1]==y]=x; 35 fa[c[x][r]]=y;fa[y]=x;fa[x]=z; 36 c[y][l]=c[x][r];c[x][r]=y; 37 pushup(y);pushup(x); 38 } 39 void splay(int x){ 40 int top=0;q[++top]=x; 41 for(int i=x;!isroot(i);i=fa[i])q[++top]=fa[i]; 42 while(top)pushdown(q[top--]); 43 while(!isroot(x)){ 44 int y=fa[x],z=fa[y]; 45 if(!isroot(y)){ 46 if(c[y][0]==x^c[z][0]==y)rotate(x);else rotate(y); 47 } 48 rotate(x); 49 } 50 } 51 void access(int x){for(int t=0;x;t=x,x=fa[x])splay(x),c[x][1]=t,pushup(x);} 52 void makeroot(int x){access(x);splay(x);rev[x]^=1;} 53 void link(int x,int y){makeroot(x);fa[x]=y;splay(x);} 54 void split(int x,int y){makeroot(x);access(y);splay(y);} 55 void cut(int x,int y){split(x,y);c[y][0]=fa[x]=0;pushup(y);} 56 int find(int x){access(x);splay(x);int y=x;while(c[y][0])y=c[y][0];return y;} 57 int querymax(int x,int y){split(x,y);return maxv[y];} 58 }; 59 Link_Cut_Tree T; 60 inline int read(){ 61 int f=1,x=0;char ch; 62 do{ch=getchar();if(ch=='0')f=-1;}while(ch<'0'||ch>'9'); 63 do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9'); 64 return f*x; 65 } 66 int main(){ 67 n=read();m=read(); 68 for(int i=1;i<=n+10;i++)f[i]=i; 69 for(int i=1;i<=m;i++){ 70 G[i].u=read(),G[i].v=read(),G[i].w=read();G[i].c=read(); 71 } 72 sort(G+1,G+m+1); 73 int tot=0; 74 for(int i=1;i<=m;i++){ 75 int u=G[i].u,v=G[i].v,w=G[i].w,c=G[i].c; 76 if(find(u)==find(v)){ 77 int t=T.querymax(u,v); 78 if(T.val[t]>G[i].c){ 79 T.cut(t,G[t-n].u); 80 T.cut(t,G[t-n].v); 81 } 82 else { 83 if(find(1)==find(n))_=min(_,G[i].w+T.val[T.querymax(1,n)]); 84 continue; 85 } 86 } 87 else f[find(u)]=find(v); 88 T.val[n+i]=G[i].c;T.maxv[n+i]=n+i; 89 T.link(u,n+i);T.link(v,n+i); 90 if(find(1)==find(n))_=min(_,G[i].w+T.val[T.querymax(1,n)]); 91 } 92 if(_==inf)puts("-1");else printf("%d\n",_); 93 return 0; 94 }
4.uoj 共价大爷游长沙
%%%%毛爷爷,不看题解不会系列。
这题做的极其艰辛:
1.zcy:我怎么维护子树size啊…………啊………………
想了半个下午,我好像会了!去机房写!
2.zcy:我的权值怎么这么小啊,都是几十几十的东西……
wori,随机数种子选的有毒?我去uoj上找一个吧……
3.zcy:我的link怎么权值改的那么鬼啊?我看看……
wori,cut写错了,mdzz。
代码准确度啊……
#include<bits/stdc++.h> #define N 300100 using namespace std; int n,m,x,y,z; struct Link_Cut_Tree{ int c[N][2],q[N],fa[N],size[N],top,rev[N],val[N],sumv[N]; int a[N],b[N],w[N],sum; inline bool isroot(int x){return fa[x]==0||c[fa[x]][0]!=x&&c[fa[x]][1]!=x;} inline void pushdown(int x){ int l=c[x][0],r=c[x][1]; if(rev[x]){ rev[l]^=1;rev[r]^=1;rev[x]^=1; swap(c[x][0],c[x][1]); } } void pushall(int x){ if(!isroot(x))pushall(fa[x]);pushdown(x); } inline void pushup(int x){ sumv[x] = val[x]^sumv[c[x][0]]^sumv[c[x][1]]; } void rotate(int x){ int y = fa[x],g=fa[y],ch=c[y][1]==x; if (!isroot(y))c[g][c[g][1]==y]=x; c[y][ch]=c[x][!ch],fa[c[y][ch]]=y; fa[y] =x,fa[x]=g,c[x][!ch] = y; pushup(y); } void splay(int x){ int top=0;q[++top]=x; for(int i=x;!isroot(i);i=fa[i])q[++top]=fa[i]; while(top)pushdown(q[top--]); while(!isroot(x)){ int y=fa[x],z=fa[y]; if(!isroot(y)){ if(c[y][0]==x^c[z][0]==y)rotate(x);else rotate(y); } rotate(x); } pushup(x); } void access(int x){ for(int t=0;x;t=x,x=fa[x]){ splay(x);val[x]^=sumv[t]^sumv[c[x][1]]; c[x][1]=t;pushup(x); } } void makeroot(int x){access(x);splay(x);rev[x]^=1;} void link(int x,int y){ makeroot(x);makeroot(y);fa[x]=y;val[y]^=sumv[x];pushup(y); } void split(int x,int y){makeroot(x);access(y);splay(y);} void cut(int x,int y){ makeroot(y);access(y);splay(x);fa[x]=0;val[y]^=sumv[x];pushup(y); } void change(int x,int v){access(x);splay(x);val[x]^=v;pushup(x);} }T; struct Edge{ int u;int v;int w; Edge(int a=0,int b=0,int c=0){u=a;v=b;w=c;} }G[5*N]; inline int read(){ int x=0,f=1;char ch; do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9'); do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9'); return f*x; } int main(){ int id; id=read();n=read();m=read(); srand(time(NULL)); for(int i=1;i<n;i++){ x=read(),y=read(); T.link(x,y); }x=0;y=0; int tot=0,res=0; while(m--){ int opt=read(),x,y,u,v; if(opt==1){ x=read();y=read();u=read();v=read(); T.cut(x,y);T.link(u,v);//printf("%d\n",T.val[v]); } else if(opt==2){ int k=rand(); x=read(),y=read();while(!k)k=rand(); G[++tot]=Edge(x,y,k);res^=k; T.change(x,k);T.change(y,k); //printf("%d\n",T.val[y]); } else if(opt==3){ x=read(); res^=G[x].w; T.change(G[x].u,G[x].w);T.change(G[x].v,G[x].w); } else if(opt==4){ x=read();y=read(); T.makeroot(x);T.access(y); if(T.val[y]==res)puts("YES"); else puts("NO"); } //printf("%d\n",res);printf("%d\n",T.val[y]); } return 0; }
不开心,这个快把我写死的LCT居然只有100行
5.ZJOI2012 网络
这题如果单看每种颜色,我们只要建一个LCT就能轻松水过去,无非是要加个cnt
但是颜色多怎么办?考虑颜色种类很小,我们多建几个LCT不就行了么?
1 #include<bits/stdc++.h> 2 #define N 10005 3 #define M 100005 4 using namespace std; 5 int n, m, c, k, u, v, w, op, x,val[N]; 6 struct Link_Cut_Tree{ 7 int c[N][2],fa[N],rev[N],maxv[N],cnt[N],q[N]; 8 inline void pushup(int x){ 9 maxv[x]=val[x];int l=c[x][0],r=c[x][1]; 10 if(l)maxv[x]=max(maxv[x],maxv[l]); 11 if(r)maxv[x]=max(maxv[x],maxv[r]); 12 } 13 inline void pushdown(int x){ 14 int l=c[x][0],r=c[x][1]; 15 if(rev[x]){ 16 rev[l]^=1;rev[r]^=1;rev[x]^=1; 17 swap(c[x][0],c[x][1]); 18 } 19 } 20 bool isroot(int x){return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;} 21 void rotate(int x){ 22 int y=fa[x],z=fa[y],l,r; 23 l=(c[y][1])==x;r=l^1; 24 if(!isroot(y))c[z][c[z][1]==y]=x; 25 fa[c[x][r]]=y;fa[y]=x;fa[x]=z; 26 c[y][l]=c[x][r];c[x][r]=y; 27 pushup(y);pushup(x); 28 } 29 void splay(int x){ 30 int top=0;q[++top]=x; 31 for(int i=x;!isroot(i);i=fa[i])q[++top]=fa[i]; 32 while(top)pushdown(q[top--]); 33 while(!isroot(x)){ 34 int y=fa[x],z=fa[y]; 35 if(!isroot(y)){ 36 if(c[y][0]==x^c[z][0]==y)rotate(x);else rotate(y); 37 } 38 rotate(x); 39 } 40 } 41 void access(int x){for(int t=0;x;t=x,x=fa[x])splay(x),c[x][1]=t,pushup(x);} 42 void makeroot(int x){access(x);splay(x);rev[x]^=1;} 43 void link(int x,int y){cnt[x]++;cnt[y]++;makeroot(x);fa[x]=y;splay(x);} 44 void split(int x,int y){makeroot(x);access(y);splay(y);} 45 void cut(int x,int y){split(x,y);cnt[x]--;cnt[y]--;c[y][0]=fa[x]=0;pushup(y);} 46 int find(int x){access(x);splay(x);int y=x;while(c[y][0])y=c[y][0];return y;} 47 int querymax(int x,int y){split(x,y);return maxv[y];} 48 }lct[15]; 49 inline int read(){ 50 int f=1,x=0;char ch; 51 do{ch=getchar();if(ch=='0')f=-1;}while(ch<'0'||ch>'9'); 52 do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9'); 53 return f*x; 54 } 55 struct Edge{ 56 int u,v; 57 bool operator<(const Edge &x)const{if(u!=x.u)return u<x.u; 58 else return v<x.v;} 59 }; 60 map<Edge,int> mp; 61 int main(){ 62 n=read();m=read();c=read();k=read(); 63 for(int i=1;i<=n;i++)val[i]=read(); 64 for(int i=1;i<=m;i++){ 65 int u=read(),v=read(),w=read(); 66 w++; 67 Edge e1=(Edge){u,v},e2=(Edge){v,u}; 68 mp[e1]=mp[e2]=w; 69 lct[w].link(u,v); 70 } 71 while(k--){ 72 int opt=read(); 73 if(opt==0){ 74 int x=read(),w=read(); 75 val[x]=w; 76 for(int i=1;i<=c;i++)lct[i].splay(x); 77 } 78 else if(opt==1){ 79 int u=read(),v=read(),w=read(); 80 w++; 81 Edge a=(Edge){u,v},b=(Edge){v,u}; 82 if(!mp.count(a)){ 83 puts("No such edge.");continue; 84 } 85 int xxx=mp[a]; 86 if(xxx==w){ 87 puts("Success.");continue; 88 } 89 if(lct[w].cnt[u]>=2||lct[w].cnt[v]>=2){ 90 puts("Error 1.");continue; 91 } 92 if(lct[w].find(u)==lct[w].find(v)){ 93 puts("Error 2.");continue; 94 } 95 puts("Success."); 96 lct[xxx].cut(u,v);lct[w].link(u,v); 97 mp[a]=mp[b]=w; 98 }else{ 99 int w=read(),u=read(),v=read(); 100 w++; 101 if(lct[w].find(u)!=lct[w].find(v)){ 102 puts("-1");continue; 103 } 104 printf("%d\n",lct[w].querymax(u,v)); 105 } 106 } 107 return 0; 108 }