zoukankan      html  css  js  c++  java
  • LCT 模板及套路总结

    这一个月貌似已经考了无数次(LCT)了.....
    保险起见还是来一发总结吧.....

    A. LCT 模板

    (LCT) 是由大名鼎鼎的 (Tarjan) 老爷发明的。
    主要是用来维护树上路径问题的。 它的神奇之处在于可以直接把一条路径抠出来维护。
    其实就是维护树链剖分中的重链与轻链。
    网上相关教程很多,直接给板子(其实是我懒得打E_E)

    0. Splay代码:

    (LCT)是以(Splay) 为实现基础的:

    IL bool Son(RG int x){return ch[fa[x]][1] == x;}
    IL bool Isroot(RG int x){return ch[fa[x]][1]!=x && ch[fa[x]][0]!=x; }
    
    IL void Rot(RG int x){
    	RG int y = fa[x] , z = fa[y] , c = Son(x);
    	if(!Isroot(y))ch[z][Son(y)] = x; fa[x] = z;
    	ch[y][c] = ch[x][!c]; fa[ch[y][c]] = y;
    	ch[x][!c] = y; fa[y] = x; PushUp(y);
    }
    IL void Splay(RG int x){
    	RG int top = 0; stk[++top] = x;
    	for(RG int i = x; !Isroot(i); i = fa[i])stk[++top] = fa[i];
    	while(top)PushDown(stk[top--]);
    	for(RG int y = fa[x]; !Isroot(x) ; Rot(x) , y = fa[x])
    		if(!Isroot(y))Son(x) ^ Son(y) ? Rot(x) : Rot(y);
    	PushUp(x); return;
    }
    

    1. Access(x) 操作

    作用是把 (x) 到根节点的这条路径变为重链。

    void Access(int x){
         for(RG int y=0;x;y=x,x=fa[x])
             Splay(x) , ch[x][1] = y , PushUp(x);
         }
    }
    

    2. MakeRoot(x) 操作

    作用是使 (x) 节点成为原树中的根。

    IL void Makeroot(RG int x){Access(x); Splay(x); Reverse(x);}
    

    对应的辅助函数:

    IL bool Reverse(RG int x){swap(ch[x][1],ch[x][0]); rev[x] ^= 1;}
    
    IL void PushDown(RG int x){
        if(rev[x])
            rev[x] ^= 1 ,
            Reverse(ch[x][0]) , Reverse(ch[x][1]) ;
    }
    

    3. FindRoot(x) 操作

    作用是找到(x)所在原树的根节点。

    IL int FindRoot(RG int x){
        Access(x); Splay(x); 
        while(ch[x][0])x=ch[x][0]; return x;
    }
    

    4. Split(x,y) 操作

    作用是分离出(x)(y)这条路径。

    IL void Split(RG int x,RG int y){MakeRoot(x); Access(y); Splay(y);}
    

    那么此时路径信息都存在节点 (y) 上了,要查询什么直接查即可。

    5. Link(x,y) 操作

    作用是连接两个结点(x) , (y)

    IL void Link(RG int x,RG int y){if((!x)||(!y))return; MakeRoot(x); fa[x] = y; }
    

    6. Cut(x,y) 操作

    作用是删除(x) , (y) 之间的连边

    IL void Cut(RG int x,RG int y){if((!x)||(!y))return; Split(x,y); ch[y][0] = fa[x] = 0; PushUp(y);}
    

    7. PushUp(x) PushDown(x) 操作

    其实是与线段树一样的,直接维护题目所求即可。
    代码略,以实际题目为准(注意翻转左右儿子的(PushDown)是必须有的)。

    8.贴一发完整的(LCT)模版

    
    namespace Link_Cut_Tree{
    	
    	bool Son(RG int x){return ch[fa[x]][1] == x;}
    	bool Isroot(RG int x){return ch[fa[x]][1]!=x && ch[fa[x]][0]!=x; }
    	bool Reverse(RG int x){swap(ch[x][1],ch[x][0]); rev[x] ^= 1;}
    
    	void PushUp(RG int x){ ....... }
    	void PushDown(RG int x)
            {if(rev[x])Reverse(ch[x][0]) , Reverse(ch[x][1]) , rev[x] ^= 1;}
    
    	void Rot(RG int x){
    		RG int y = fa[x] , z = fa[y] , c = Son(x);
    		if(!Isroot(y))ch[z][Son(y)] = x; fa[x] = z;
    		ch[y][c] = ch[x][!c]; fa[ch[y][c]] = y;
    		ch[x][!c] = y; fa[y] = x; PushUp(y);
    	}
    	void Splay(RG int x){
    		RG int top = 0; stk[++top] = x;
    		for(RG int i = x; !Isroot(i) ; i = fa[i])stk[++top] = fa[i];
    		while(top)PushDown(stk[top--]);
    		for(RG int y = fa[x]; !Isroot(x) ; Rot(x) , y = fa[x])
    			if(!Isroot(y))Son(x) ^ Son(y) ? Rot(x) : Rot(y);
    		PushUp(x); return;
    	}
    	
    	void Access(RG int x)
    	    { for(RG int y = 0; x; y = x,x = fa[x])Splay(x),ch[x][1] = y,PushUp(x); }
        void MakeRoot(RG int x){Access(x); Splay(x); Reverse(x);}
        int FindRoot(RG int x){Access(x); Splay(x); while(ch[x][0])x=ch[x][0]; return x;}
        void Split(RG int x,RG int y){MakeRoot(x); Access(y); Splay(y);}
        void Link(RG int x,RG int y){if((!x)||(!y))return; MakeRoot(x); fa[x] = y;  }
        void Cut(RG int x,RG int y){if((!x)||(!y))return; Split(x,y); ch[y][0] = fa[x] = 0; PushUp(y);}
    
    }
    

    B. LCT 维护 边权 信息

    具体的实现方法有几种,这里只讲一种最简单、最常用的方法。
    对于每一条边,新建一个点,然后连向对应的两个原树节点。
    那么常见的维护方法是 代表点的节点不赋值,只有代表边的节点才赋值。
    具体可以见这一道非常经典的题目: [NOI2014]魔法森林
    伪代码为:
    (Link)操作直接进行即可。

    IL void Link(Node u,Node v)
       NewNode(val_edge) ---(give)---> code_edge
       Link(u,code_edge);  Link(v,code_edge);
    

    (Cut)操作则需要找到这条边对应的两个节点,然后直接删除即可。

    IL void Cut(Edge e)
        Edge e ---(find)--> Node u1,u2 ;  
        Delete(u1,code_e);  Delete(u2,code_e);
    

    这里注意 :
    一定要找到对应的节点,而不是code_e的左右儿子!! ,不然保准你 WA 的怀疑人生。
    然后一个非常经典的应用 就是 动态求图的割边(桥):
    具体见这题:[AHOI2005]LANE 航线规划 ;;;;;;;;

    C. LCT 维护 子树 信息

    理论上来说(LCT)是不适合维护子树信息的,但总有一些毒瘤的出题人**..
    给一篇写的非常详细的博客:http://blog.csdn.net/neither_nor/article/details/52979425
    怎么搞呢?
    先明确一下概念。
    (LCT) 链剖后,一些路径变为了 单独的 一棵(Splay)
    我们类似树链剖分,把节点划为实儿子、虚儿子。

    实儿子就是在同一棵(Splay)里的那个儿子,其它则为虚儿子。
    那么实儿子对应的子树就为实子树,虚儿子对应的子树就为虚子树。
    .
    其实我们原来的(LCT)就是维护了实子树对吧?
    所以我们在维护子树信息时,只要再维护一下虚子树的信息即可。
    以求子树大小为例。
    我们假设(sz[u])表示(u)虚子树大小 , (sum[u])表示整个子树大小,
    那么显然,我们(PushUp)时,只需要更新(sum) , (sz)我们手动维护。

    IL void PushUp(RG int x){ sum[x] = sum[ch[x][0]]+sum[ch[x][1]]+sz[x]+1; }
    

    那么考虑什么情况下会改变虚子树的大小,其实只有两个:

    1. Access'(x) 操作

    我们会把 (x) 原来的实儿子变为虚儿子,把另一个虚儿子变为实儿子。
    我们对应的修改一下(x)(sz)值,然后(PushUp)修改(sum)值即可。

    IL void Access(RG int x){
    	for(RG int y = 0; x; y = x,x = fa[x]){
    		Splay(x);
    		sz[x] += sum[ch[x][1]] - sum[y];
    		ch[x][1] = y; PushUp(x); 
    	}return;
    }
    

    2. Link'(x,y) 操作

    我们把(x)变为(y)的儿子。
    那么显然(x)以及其所有祖先的(sz)值都会收到影响。
    解决办法非常简单,把(y)(MakeRoot) 一下即可使得(x)只影响 (y)
    注意一下由于我们修改了(y)(sz)值,所以要(PushUp)一下(y)节点。

    IL void Link(RG int x,RG int y){
            if((!x)||(!y))return; 
    	MakeRoot(x); MakeRoot(y);
    	fa[x] = y; sz[y] += sum[x]; PushUp(y); 
    }
    

    3.应用

    应用一般就是求解会不断换根的子树信息问题。
    最经典的一道题目是:「BJOI2014」大融合

    D.LCT 维护图上信息

    理论上来说(LCT)也是不适合维护图上信息的,但总有一些毒瘤的出题人..
    图上信息的维护是不支持删边操作的。
    其实图与树的差别就是可能有重边、环之类的。
    那么既然我们只需要支持加边操作,那么只要用并查集维护缩点即可。
    怎么维护呢? 这其实也是维护双联通分量的套路了。
    .
    开两个并查集:
    (1)(bzj[u][1]),用于维护连通性
    每次(Link)前,先查找一下两个点是否联通,如果不连通,则直接相连即可。
    如果联通,则需要用到第二个并查集:
    (2)(bzj[u][2]),用于维护双联通性
    如果两个点已经双联通了,那么再加边其实对双联通性没有影响。
    如果两个点的(bzj2)未相连,那么把两个的(bzj2)相连,然后缩点。
    如果两个点的(bzj2)已相连,那么说明这两个点已经缩在一起了,所以无需连边。
    .
    那么此时(bzj2)其实就是每个点对应的缩点后的点(id)了。
    然后是实现,实现的时候,想一想(LCT)的性质,我们可以发现:
    只有向上跳父亲的时候,缩点才会对操作有影响
    为了方便描述,定义一个(F)函数用于找到一个点(u)的对应(id)

    int Find(RG int x,RG int od){return(bzj[x][od]==x)?x:Find(bzj[x][od],od);}
    int F(RG int x){return Find(x,2); }
    

    然后正常的(LCT)操作都遵循上面那个结论,找父亲是(Find)一下即可。
    (Splay)为例(什么(Access)之类的都是一样的):

    IL void Splay(RG int x){
        RG int top = 0; stk[++top] = x;
        for(RG int i = x; !Isroot(i); i = F(fa[i]))stk[++top] = F(fa[i]); 
        while(top)PushDown(stk[top--]);
        for(RG int y = F(fa[x]); !Isroot(x); Rot(x),y = F(fa[x]))
            if(!Isroot(y))Son(x) ^ Son(y) ? Rot(x) : Rot(y);
        PushUp(x);  return;
    }
    

    关键:(Add(x,y)) 操作

    关键在于链接两个点时的操作,这时候是不能直接执行(Link)操作的。
    先给代码:

    IL void Add(RG int u,RG int v){
        RG int x = F(u) , y = F(v);
        RG int f1 = Find(x,1) , f2 = Find(y,1);
        if(f1 != f2){
            bzj[f1][1] = f2;
            Link(x,y);  return;
        }
        else{
            if(x == y)return;
            Split(x,y); Merge(y,y);   //缩点
            ch[y][0] = ch[y][1] = 0;  //!!!!!!!!
        }return;
    }
    

    显然就是按照上面的并查集维护原则连边。
    然后给一下缩点的代码:

    IL void Merge(RG int u,RG int rt){
        if(u ^ rt)
            sum[rt] += sum[u], bzj[F(u)][2] = rt, sum[u] = fig[u] = 0;
        if(ch[u][0])Merge(ch[u][0],rt);
        if(ch[u][1])Merge(ch[u][1],rt);
    }
    

    其中(sum)为结点自身的信息(本身值),(fig)为子树信息(计算值)。

    应用:

    这个就依题目而定吧。
    请记住一个最明显的特征:只支持加边,不支持删边!
    (LCT)维护图上信息最经典的一题为:[BZOJ 2959]长跑 , 友情提示注意卡常。

    有根LCT

    自行yy,唯一一个需要注意的地方:
    Cut(u,Fa) 的时候,必须把(fa)转到(Splay)顶端!(不能把(u)转到顶端),然后剪切。

    IL void Cut(int u , int Fa) {
        Access(u) ; Splay(Fa) ;
        ch[1][Fa] = fa[u] = 0 ; PushUp(Fa) ; return ; 
    }
    

    因为如果转(u)到顶端的话,剪切后整棵树的根就会产生变化,转移到(u)所属(Splay)中。

  • 相关阅读:
    中国历史朝代公元对照简表
    [Solved] DashBoard – Excel Service: The data sources may be unreachable, may not be responding, or may have denied you access.
    Delete/Remove Project from TFS 2010
    Sharepoint site showing system account instead of my username on the top right corner.
    你的成功在于你每天养成的习惯
    Internet Information Services is running in 32bit emulation mode. Correct the issue listed above and rerun setup.
    Prepare to back up and restore a farm (Office SharePoint Server 2007)
    Word中字号与磅值的对应关系
    How to: Change the Frequency for Refreshing the Data Warehouse for Team System
    UI Automation in WPF/Silverlight
  • 原文地址:https://www.cnblogs.com/GuessYCB/p/8330024.html
Copyright © 2011-2022 走看看