zoukankan      html  css  js  c++  java
  • 【瞎口胡】Link-Cut Tree

    Link Cut Tree 是一种神奇的数据结构。感觉完全不会,写下模板题的学习笔记。

    LCT 中,比较重要的一点是将树边划分成实边和虚边。

    对于每个非叶节点 (u),我们选择一个儿子 (v) 作为 (u)实儿子((u,v))实边(u) 到其它儿子的连边是虚边。

    容易发现,实边组成了若干条实链,每个节点恰好在实链中出现了一次(链上只有一个节点仍然算作实链)。

    image

    上图中,((1,2,5,7),(3,8),(4,6)) 是这棵树的实链。

    对于每一条实链,我们用一棵 Splay 来维护节点。这棵 Splay 的中序遍历中,节点深度单调递增。

    对于上图中的实链剖分,对应的 Splay 森林(注意形态可能不一样,但满足中序遍历节点深度单调递增):

    image

    上图中绿色的边是虚边。

    有一个显然的性质:如果 ((u,v)) 是实边且 (u) 的深度小于 (v),那么 (v) 是 Splay 中 (u) 的儿子。无论 ((u,v)) 是否是实边,(v) 的父亲都指向 (u)

    Access ~ 点与根的连接

    LCT 的核心操作是 access()access(x) 改变了原树的实链剖分,同时构造出一棵中序遍历以根节点开始,以 (x) 结束的 Splay。

    首先把 (x)(x) 的实儿子的连边改成虚边,因为要构造的 Splay 中序遍历以 (x) 结束。然后不断地从深度较大的 Splay 跳到深度较小的 Splay,并把跳的过程中经过的所有虚边改为实边。

    inline void access(int x){
    	for(rr int y=0;x;y=x,x=fa(x)){
    		splay(x),rc(x)=y,update(x);
    	}
    }
    

    Makeroot ~ 换根

    有了这个操作之后,我们可以来换根。makeroot(x) 的定义是让 (x) 成为原树的根。

    我们怎么做呢?观察到,不在 (x) 到原来根路径上的点不会受到影响。于是我们只需要把 (x) 到原来根路径上的点拉出来,这就是 access(x)。然后我们将 (x) 旋转上去,这样 (x) 只有左子树(LCT 中 Splay 的性质:中序遍历节点深度单调递增)。我们想让 (x) 变成根,只需要翻转这棵 Splay。和做文艺平衡树时一样,打上翻转标记就行。

    inline void rev(int x){
    	if(!x)
    		return;
    	std::swap(lc(x),rc(x));
    	tree[x].tag^=1;
    	return;
    }
    inline void makeroot(int x){
    	access(x),splay(x),rev(x);
    	return;
    }
    

    Findroot ~ 找根

    findroot(x) 的定义是找到 (x) 所在树的根。因为维护过程中可能会出现森林,于是这个函数对判断连通性很有帮助。

    access(x) 之后再 splay(x),按照上文,此时 (x) 只有左子树。往左子树一直走,就走到了根。

    为了保证复杂度,找到原树的根之后要把根 splay() 成为 Splay 的根。

    inline int findroot(int x){
    	access(x),splay(x);
    	while(lc(x))
    		pushdown(x),x=lc(x); // 记得下放翻转标记
    	splay(x);
    	return x;
    }
    

    Split ~ 提取路径

    split(x,y) 的定义是拉出 (x,y) 在原树上的路径成为一个 Splay。通常要保证 (x,y) 连通。

    很简单,makeroot(x) 之后在 access(y) 即可。为了保证复杂度,需要 splay(y)

    inline void split(int x,int y){
    	makeroot(x),access(y),splay(y);
    	return;
    }
    

    link(x,y) 的定义是连接 (x,y)。如果 (x,y) 已经连通,则无需连接。

    也很简单,因为是无根树,所以我们并不在乎 (x)(y) 的祖孙关系。默认 (x) 的父亲变成 (y)makeroot(x) 再将 (x) 的父亲设为 (y) 就行。

    如果 (x)(y) 连通,那么 makeroot(x) 之后, findroot(y) 的结果一定为 (x)。特判即可。

    inline void link(int x,int y){
    	makeroot(x);
    	if(findroot(y)==x){
    		return;
    	}
    	fa(x)=y,update(y); // 记得 update
    	return;
    }
    

    Cut ~ 断边

    cut(x,y) 的定义是断掉 (x,y) 在原树上的边。如果没有边就不断。

    首先用 findroot(y) 判掉 (x)(y) 不连通的情况。接下来讨论 (x)(y) 连通(即 findroot(y)==x)时:

    findroot(y) 时已经执行了 access(y) 操作(见 findroot() 的实现),因此 (x)(y) 的路径已经组成了一棵 Splay。此时若 (x,y) 之间有边,则:(x)(y) 的父亲;(y) 没有左子树,否则这些点就会插在中序遍历中 (x)(y) 的中间。

    inline void cut(int x,int y){
    	makeroot(x);
    	if(findroot(y)==x&&fa(y)==x&&!lc(y)){
    		rc(x)=fa(y)=0;
    		update(x); // 记得 update
    	}
    	return;
    }
    

    模板题 Luogu P3690

    题意

    给定 (n) 个点以及每个点的权值,要你处理接下来的 (m) 个操作。

    操作有四种,操作从 (0)(3) 编号。点从 (1)(n) 编号。

    • 0 x y 代表询问从 (x)(y) 的路径上的点的权值的 ( ext{xor}) 和。保证 (x)(y) 是联通的。
    • 1 x y 代表连接 (x)(y),若 (x)(y) 已经联通则无需连接。
    • 2 x y 代表删除边 ((x,y)),不保证边 ((x,y)) 存在。
    • 3 x y 代表将点 (x) 上的权值变成 (y)

    (n,m leq 3 imes 10^5),值域 (10^9)

    题解

    update(x) 改成维护异或和就好了。

    询问操作就是 split 出来之后 Splay 上点 (y) 的异或和(因为我们 Splay 过)。

    (1,2) 操作都是板子。(3) 操作的话需要先把 (x) splay 上去再修改,否则 (x) 在 Splay 上父亲的信息都没有得到更新。

    # include <bits/stdc++.h>
    
    const int N=100010,INF=0x3f3f3f3f;
    
    struct Node{
    	int son[2];
    	int val,sum,tag,fa;
    }tree[N];
    int sta[N];
    inline int read(void){
    	int res,f=1;
    	char c;
    	while((c=getchar())<'0'||c>'9')
    		if(c=='-')f=-1;
    	res=c-48;
    	while((c=getchar())>='0'&&c<='9')
    		res=res*10+c-48;
    	return res*f;
    }
    inline int& fa(int x){
    	return tree[x].fa;
    }
    inline int& lc(int x){
    	return tree[x].son[0];
    }
    inline int& rc(int x){
    	return tree[x].son[1];
    }
    inline void update(int x){
    	tree[x].sum=tree[lc(x)].sum^tree[rc(x)].sum^tree[x].val;
    	return;
    }
    inline bool nroot(int x){
    	return (lc(fa(x))==x)||(rc(fa(x))==x);
    }
    inline void rotate(int x){
    	if(!nroot(x))
    		return;
    	int y=fa(x),z=fa(y),k=(rc(y)==x),ns=tree[x].son[!k];
    	if(nroot(y))
    		tree[z].son[rc(z)==y]=x;
    	tree[x].son[!k]=y,tree[y].son[k]=ns;
    	if(ns)
    		fa(ns)=y;
    	fa(y)=x,fa(x)=z;
    	update(y),update(x); // 顺序注意 
    	return;
    }
    inline void rev(int x){
    	if(!x)
    		return;
    	std::swap(lc(x),rc(x));
    	tree[x].tag^=1;
    	return;
    }
    inline void pushdown(int x){
    	if(!tree[x].tag)
    		return;
    	rev(lc(x)),rev(rc(x)),tree[x].tag=0;
    	return;
    }
    inline void splay(int x){
    	int top=0;
    	int y=x;
    	for(;;){ // 记得这里要先下放标记
    		sta[++top]=y;
    		if(!nroot(y))
    			break;		
    		y=fa(y);
    	}
    	while(top)
    		pushdown(sta[top--]);
    	while(nroot(x)){
    		int y=fa(x),z=fa(y);
    		if(nroot(y)){
    			((rc(y)==x)==(rc(z)==y))?rotate(y):rotate(x);
    		}
    		rotate(x);
    	}
    	return;
    }
    inline void access(int x){
    	for(int y=0;x;y=x,x=fa(x)){
    		splay(x),rc(x)=y,update(x);
    	}
    	return;
    }
    inline void makeroot(int x){
    	access(x),splay(x),rev(x);
    	return;
    }
    inline int findroot(int x){
    	access(x),splay(x);
    	while(lc(x))
    		pushdown(x),x=lc(x);
    	splay(x);
    	return x;
    }
    inline void split(int x,int y){
    	makeroot(x),access(y),splay(y);
    	return;
    }
    inline void link(int x,int y){
    	makeroot(x);
    	if(findroot(y)==x){
    		return;
    	}
    	fa(x)=y,update(y);
    	return;
    }
    inline void cut(int x,int y){
    	makeroot(x);
    	if(findroot(y)==x&&rc(x)==y&&!lc(y)){ // 顺序不能反 
    		fa(y)=0,rc(x)=0;
    		update(x);
    	}
    	return;
    }
    int main(void){
    	int n=read(),m=read();
    	for(int i=1;i<=n;++i){
    		tree[i].sum=tree[i].val=read();
    	}
    	int x,y,opt;
    	while(m--){
    		opt=read(),x=read(),y=read();
    		switch(opt){
    			case 0:{
    				split(x,y);
    				printf("%d
    ",tree[y].sum);
    				break;
    			}
    			case 1:{
    				link(x,y);
    				break;
    			}
    			case 2:{
    				cut(x,y);
    				break;
    			}
    			case 3:{
    				splay(x),tree[x].val=y,update(x);
    				break;
    			}
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    状态模式
    maven-war-plugin 插件 web.xml 缺失时忽略
    Java远程方法协议(JRMP)
    Java Singleton的3种实现方式
    浅谈分布式消息技术 Kafka
    浅谈分布式事务
    J2EE开发时的包命名规则,养成良好的开发习惯
    使用Dom4j创建xml文档
    Java HttpClient Basic Credential 认证
    Spring MVC的Post请求参数中文乱码的原因&处理
  • 原文地址:https://www.cnblogs.com/liuzongxin/p/13576898.html
Copyright © 2011-2022 走看看