zoukankan      html  css  js  c++  java
  • Link Cut Tree 动态树

    干啥用的

    对于一个森林,查询链上信息、

    前置芝士:splay

    P3690 【模板】Link Cut Tree (动态树)

    (开始大量盗图)

    首先确定LCT的一些基本概念

    • 实边:每个点最多连一条实边(类似树剖),这个边是随便连的,而且是可以变的、虚边:不是实边的边
    • 实链:对于每个实边构成的链(这条链必须是从上到下深度递增),开一个splay,存的是树上的节点,按深度排序(中序遍历为深度递增),平衡树上维护的信息就是这一条链的信息

    对于每个节点,(fa[x])表示父亲(实链则表示splay上的父亲,否则表示虚链上的父亲),(son[x][0/1])表示splay上的左右儿子,不记录虚链的儿子

    这样判断是否为当前这个splay的根的方法就变为

    inline bool is_root(int x){return son[fa[x]][0]!=x&&son[fa[x]][1]!=x;}
    

    这一棵树

    按splay划分就是

    因为有翻转操作(之后会提到哪里会用),所以需要用到文艺平衡树

    int son[N][2],val[N],sum[N],tag[N],fa[N];
    inline void update(int x){sum[x]=sum[son[x][0]]^sum[son[x][1]]^val[x];}
    inline void rev(int x){swap(son[x][1],son[x][0]);tag[x]^=1;}
    inline void pushdown(int x){
    	if(!tag[x])return;tag[x]=0;
    	if(son[x][0])rev(son[x][0]);
    	if(son[x][1])rev(son[x][1]);
    }
    inline int id(int x){return x==son[fa[x]][1];}
    inline void rotate(int x){
    	int f=fa[x],gf=fa[f],idf=id(f),idx=id(x);
    	if(!is_root(f))son[gf][idf]=x;fa[x]=gf;
    	son[f][idx]=son[x][idx^1];son[x][idx^1]=f;
    	if(son[f][idx])fa[son[f][idx]]=f;fa[f]=x;
    	update(f);update(x);
    }
    int stac[N],top;
    inline void splay(int x){
    	top=0;int cur=x;stac[++top]=x;
    	while(!is_root(cur))cur=fa[cur],stac[++top]=cur;
    	while(top)pushdown(stac[top--]);//从上到下下放标记
    	while(!is_root(x)){
    		if(!is_root(fa[x])){
    			if(id(fa[x])==id(x))rotate(fa[x]);
    			else rotate(x);
    		}
    		rotate(x);
    	}
    }
    

    (access(x))

    LCT基本操作,让(x)到根的路径上为一条实链

    目标:

    依次完成:

    把自己变为根,断掉原来的儿子,向上

    把自己变为根,替换掉原来的儿子,向上

    把自己变为根,替换掉原来的儿子,向上

    把自己变为根,替换掉原来的儿子,向上

    直到合并到根为止

    inline void access(int x){
    	for(int y=0;x;y=x,x=fa[x])
    		splay(x),son[x][1]=y,update(x);
    }
    

    其余操作都在(splay)(access)基础上

    (make\_root(x))

    (x)成为根

    先打通(x)到根的路径,然后让(x)成为当前(splay)的根

    但是这时的(splay)是按深度递增的,没有变,要变成按深度递减,那么就需要把这棵树翻转一下

    inline void make_root(int x){
    	access(x);	splay(x);	rev(x);
    }
    

    (find\_root(x))

    找到(x)所在树的根

    先打通(x)到根的路径,找到这个splay的深度最低的点即可(注意(pushdown)

    inline int find_root(int x){
    	access(x);	splay(x);	pushdown(x);
    	while(son[x][0])x=son[x][0],pushdown(x);
    	splay(x);
    	return x;
    }
    

    (link(x,y))

    先让(x)成为根,判断一下(y)的根是不是(x)

    是的话就已经在一棵树内,不用连边

    否则连一条虚边

    inline void link(int x,int y){
    	make_root(x);
    	if(find_root(y)==x)return ;
    	fa[x]=y;
    }
    

    (cut(x,y))

    同样的先把(x)作为根,判断一下(y)在不在(x)的子树内

    注意这里调用了(find\_root(y)),若(y)(x)的子树内,那么(x)(y)的链已经打通,(x,y)在同一颗(splay)内,且这棵(splay)的根为(x)

    那么如果(y)(x)有连边,那么(y)的深度等于(dep[x]+1),又因为splay维护一条链的信息,这个(dep[x]+1=dep[y])的点事唯一的,即(y)一定是(x)的右儿子,且(y)没有左儿子(不存在在(x,y)之间的点)

    inline void cut(int x,int y){
    	make_root(x);
    	if(find_root(y)!=x||fa[y]!=x||son[y][0])return;
    	fa[y]=son[x][1]=0;update(x);
    	return ;
    }
    

    (query(x,y))

    (x)为根,连上(x,y)的边,以(x/y)为splay的根

    这样跟的信息就是整条链的信息

    inline int query(int x,int y){
    	make_root(x);access(y);splay(y);
    	return sum[y];
    }
    

    (modify(x,val))

    修改信息

    即把(x)作为其所在(splay)的根,然后跟新自己的(val),最后(update)即可

    inline void modify(int x,int v){
    	splay(x);val[x]=v;update(x);
    }
    
  • 相关阅读:
    人生本来就是一种修行
    Go的一些趣味题库
    PHP系统常被挂马的代码
    PHP加密字符串函数(解密)
    photoshop
    截图
    用手机作为摄像头
    IM 学习记录
    编译 学习过程
    过程流水记录-编译Lua srlua使用iup-完结
  • 原文地址:https://www.cnblogs.com/harryzhr/p/14256055.html
Copyright © 2011-2022 走看看