【动态树】动态树的应用 (总)
一、维护链的信息
通过split在树中处理出一条链,y点所储存的值就是答案
当要处理树的边权相关的问题时,我们可以化一条边为一个带权的点,就可以一般化处理了
二、动态维护连通性&强联通分量
维护连通性:findroot进行判断,其他就是link、cut的操作了
维护强联通分量:如果要连结A和B,若发现A和B在同一颗LCT中,则dfs缩点,就是把形成的环的fa指针指向A或B,但要注意在access时要改为
1 void access (int x) 2 { 3 for (int y=0;x;y=x,x=fa[y]=find (fa[x])) 4 splay (x),ch[x][1]=y,update (x); 5 }
维护强联通分量只支持加边,同时我们可以把动态删边离线倒序成动态加边
三、维护边权(常用于维护生成树)
利用LCT我们就可以动态维护一颗生成树
例如维护最小生成树:在一棵树中加入一条边时,会产生一个环,我们只需删去换上权值最大的一条边,在加入这条边即可
四、维护虚子树信息总和与原树信息总和
如果题目要我们维护一颗树的总size,我们就要引入虚子树;
原来我们update这样写:
1 void update (int x) 2 { 3 size[x]=size[ch[x][0]]+size[ch[x][1]]+1; 4 }
现在我们新增一个数组si [u],用来维护u虚边所连的子树的size总和,显然一颗树的总size为si [u]+size[u]
所以我们把update改为:
1 void update (int x) 2 { 3 size[x]=size[ch[x][0]]+size[ch[x][1]]+si[x]+1; 4 }
然后我们怎么维护si [MAXN]数组呢?
在link,access我们做出以下修改
1 void access (int x) 2 { 3 for (int y=0;x;x=fa[y=x]) 4 splay (x),si[x]+=size[ch[x][1]],si[x]-=size[ch[x][1]=y],update (x); 5 } 6 void link (int x,int y) 7 { 8 makeroot (x),access (y),splay (y),si[fa[x]=y]+=size[x],update (y); 9 }
在access时,会有虚边和实边的转化,我们进行删除和增加即可
同样link我们也如此操作,但我们要先将y变为没有fa的点,不然y的size与si改了,但它的祖先没有更改
五、维护树上染色联通块
我们可以对于每个联通快都开一个LCT,但如果数据是菊花图,肯定会被卡爆
因为树的形态是不会改变的,所以我们选定一个根,将无根树转化为有根树
这里以黑白为例:
我们在开两个LCT,一个存白色的联通块,一个存黑色联通块;
因为对于每个点,只有一条连父亲的边(根结点不连)
每个点有两个状态:
1、该点为白色,在白LCT中该点link其父亲结点
2、该点为黑色,在黑LCT中该点link其父亲结点
很显然,每个结点的颜色发生修改时我们只需在两棵LCT中修改对应父亲结点的边即可
所以时间复杂度可以保证O(QlogN)
图中通过父子连边形成了若干个联通块,我们达成了第一步
因为是父子连边,我们要先dfs一遍
1 void dfs (int u,int f) 2 { 3 for (int i=head[u];i!=0;i=e[i].nxt) 4 if (e[i].v!=f) 5 { 6 LCT[col[1]].link (e[i].v,u),fa[e[i].v]=u; 7 dfs (e[i].v,u); 8 } 9 }
我们在link,cut要修改一下
1 void link (int x,int y) 2 { 3 if (!y) return; 4 access (y),splay (x),splay (y); 5 fa[x]=y,si[y]+=size[x],update (y); 6 } 7 void cut (int x,int y) 8 { 9 if (!y) return; 10 access (x),splay (x); 11 ch[x][0]=fa[ch[x][0]]=0,update (x); 12 }
因为树根是固定的,所以切记不能使用makeroot,在splay时也不需pushdown
1 void splay (int x) 2 { 3 while (nroot (x)) 4 { 5 int y=fa[x]; 6 if (nroot (y)) rotate (get (x)==get (y)?y:x); 7 rotate (x); 8 } 9 update (x); 10 }
其中update一定要加,因为如果该树中只有x一个结点,便不会rotate,size[x]和si[x]便不会改变,仍为0,导致WA
或者在初始定义时 for (int i=1;i<=n;i++) size[i]=1;定义一遍
操作代码:
要特判根结点的颜色是否与当前结点相同,否则除去根结点的答案
1 while (m--) 2 { 3 int opt=read (),x=read (); 4 if (opt==1) 5 LCT[col[x]].cut (x,fa[x]),col[x]^=1,LCT[col[x]].link (x,fa[x]); 6 else 7 { 8 LCT[col[x]].access (x);int rt=LCT[col[x]].findroot (x); 9 if (col[rt]==col[x]) printf ("%d ",LCT[col[x]].size[rt]); 10 else printf ("%d ",LCT[col[x]].size[LCT[col[x]].ch[rt][1]]); 11 } 12 }