Fork me on GitHub

【动态树】动态树的应用 (总)

【动态树】动态树的应用 (总)

 

一、维护链的信息

通过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\n",LCT[col[x]].size[rt]);
10         else printf ("%d\n",LCT[col[x]].size[LCT[col[x]].ch[rt][1]]);
11     }
12 }

 

posted @ 2018-12-04 14:36  Paul·Shi  阅读(1074)  评论(0编辑  收藏  举报