zoukankan      html  css  js  c++  java
  • [学习笔记] Link-Cut-Tree

    模板题点这里

    大体思路

    可以看到,(LCT)就是用于解决这一类问题的,下面我们就来看一下它是怎么实现的。

    我们知道有一种叫做树剖的东西,这玩意儿好像可以支持链上的一些操作。

    我们还知道有一种叫做(Splay)的东西,这玩意儿貌似可以可以通过瞎搞完成很多动态的操作。

    要不?让他们生个孩子?!

    XD

    嗯反正大致思路就是这样子的啦!

    一些定义

    Preferred Child: 重儿子,重儿子与父亲节点在同一棵Splay中,一个节点最多只能有一个重儿子。

    Preferred Edge: 重边,连接父亲节点和重儿子的边。

    Preferred Path : 重链,由重边及重边连接的节点构成的链。

    Auxiliary Tree(辅助树):

    • 由一条重链上的所有节点所构成的(Splay)称作这条链的辅助树。

    • 每个点的键值为这个点的深度,即这棵(Splay)的中序遍历是这条链从链顶到链底的所有节点构成的序列

    • 辅助树的根节点的父亲指向链顶的父亲节点,然而链顶的父亲节点的儿子并不指向辅助树的根节点
      (也就是说父亲不认轻儿子只认重儿子,儿子都认父亲)

    具体实现

    假如我们现在有两种操作:

    • (access(x))可以让你把(x)到当前实树的根的路径全部变成重链,也就是说,(x)到根路径上的所有点都会在一棵(Auxiliary Tree)中,而这可(Auxiliary Tree)上只有这些节点

    • (makert(x))可以让你把(x)节点当做实树中的根(因为是树嘛,所以根是可以变的)

    (Query)

    有了这两种逆天操作,那么我们现在应该怎么维护题中的询问操作呢?

    显然,我们只需要把(x)(y)路径上的那些点都放到一棵(Auxiliary Tree)即可。

    那么我们就可以先把(x)作为实树的根,然后再把(y)(x)之间的路径打通即可,这样这些点就全部在同一颗(Auxiliary Tree)上了。

    如果要求值的话就(Splay(x/y)),然后你的(Sum[x/y])就是答案了。

    void query(int x,int y){
    	makert(x),access(y),splay(y);
    }
    

    (Findrt)

    我们应该怎么寻找包含点(x)的实树的根呢?

    我们只要先(access(x))打通(x)到根的路径,再(Splay(x)),然后一直往左找就是根了。

    因为根的深度是最小的嘛!

    int findrt(int x){
    	access(x),splay(x);
    	while (ch[x][0]) pushdown(x),x=ch[x][0]; 
    	splay(x);
    	return x;
    }
    

    (Link)

    连边操作,想想应该怎么搞。

    很简单 (是真的简单) ,先判断一下(x)(y)两点是不是在一颗实树内,如果不在的话把他们连上就是了,当然,连的是虚边,儿子认爸爸,爸爸不认儿子的那种。

    void link(int x,int y){
    	makert(x),fa[x]=y;
    }
    if (findrt(x)!=findrt(y)) link(x,y);
    

    (Cut)

    先判一判他们两个点之间是不是有边连接。

    怎么判呢?

    先把(x)变成根,再打通(x)(y)之间的路,如果(x)(y)之间直接就有连边的话,点(y)肯定就是在(x)的下面一层.

    那么把(y点)(Splay)到根之后(x)肯定是他的左儿子,而且点(x)是没有右儿子的,因为(x)的重儿子只有一个且只能是(y),所以不会再出现深度比(x)大,比(y)小的点。

    void cut(int x,int y){
    	makert(x),access(y),splay(y);
    	if (ch[y][0]!=x&&!ch[ch[y][0]][1]) return;
    	ch[y][0]=fa[x]=0,update(y);
    }
    

    (Access)

    相信把上面的操作都讲了一圈以后,大家对于(access)操作的理解都加深了吧,那么接下来(access)的实现方法就很简单了。

    (access)操作中,我们要做到两点:

    • 断掉父亲节点原来的重链

    • 把父亲节点和它的辅助树合并

    我们只要一直重复这两步,一直到父亲为实树的根为止(具体表现为(fa[x])(0))。

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

    (Makert)

    很简单,把点(x)到根的路径打通,然后把(x)(Splay)到根即可。

    注意,因为此时的根节点发生变换,所以所有节点的深度都会翻转,要给(x)打一个翻转标记。

    void makert(int x){
    	access(x),splay(x),rever(x);
    }
    

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=300005;
    int fa[N],ch[N][2],val[N],w[N],rev[N];
    int n,q,opt,x,y;
    int where(int x){return ch[fa[x]][1]==x;}
    bool isrt(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
    void rever(int x){rev[x]^=1,swap(ch[x][0],ch[x][1]);}
    void update(int x){val[x]=val[ch[x][0]]^val[ch[x][1]]^w[x];}
    void pushdown(int x){
    	if (rev[x]){
    		if (ch[x][0]) rever(ch[x][0]);
    		if (ch[x][1]) rever(ch[x][1]);
    		rev[x]=0;
    	}
    }
    void Allpushdown(int x){
    	if (!isrt(x)) Allpushdown(fa[x]);
    	pushdown(x);
    }
    void rotate(int x){
    	int par=fa[x],son=x,c=where(x);
    	if (!isrt(par)) ch[fa[par]][where(par)]=son;
    	fa[son]=fa[par];
    	ch[par][c]=ch[son][c^1];
    	fa[ch[par][c]]=par;
    	ch[son][c^1]=par;
    	fa[par]=son;
    	update(par),update(son);
    }
    void splay(int x){
    	Allpushdown(x);
    	for (;!isrt(x);rotate(x))
    		if (!isrt(fa[x])) rotate(where(fa[x])==where(x)?fa[x]:x);
    }
    void access(int x){
    	for (int y=0;x;y=x,x=fa[x]) splay(x),ch[x][1]=y,update(x);
    }
    void makert(int x){
    	access(x),splay(x),rever(x);
    }
    int findrt(int x){
    	access(x),splay(x);
    	while (ch[x][0]) pushdown(x),x=ch[x][0]; 
    	splay(x);
    	return x;
    }
    void link(int x,int y){
    	makert(x),fa[x]=y;
    }
    void cut(int x,int y){
    	makert(x),access(y),splay(y);
    	if (ch[y][0]!=x&&!ch[ch[y][0]][1]) return;
    	ch[y][0]=fa[x]=0,update(y);
    }
    void query(int x,int y){
    	makert(x),access(y),splay(y);
    }
    int main(){
    	scanf("%d%d",&n,&q);
    	for (int i=1;i<=n;i++) scanf("%d",&w[i]);
    	while (q--){
    		scanf("%d%d%d",&opt,&x,&y);
    		if (opt==0) query(x,y),printf("%d
    ",val[y]);
    		if (opt==1) if (findrt(x)!=findrt(y)) link(x,y);
    		if (opt==2) cut(x,y);
    		if (opt==3) w[x]=y,splay(x);
    	}
    	return 0;
    }
    
  • 相关阅读:
    SDWebImage内存性能优化
    dataGridView1数据导出
    listView数据导出
    sqlserver定时备份
    C#-IniFiles文件配置连接数据库
    (转)线程同步详解
    C#工具类:Json操作帮助类(转载)
    c#实现验证码功能
    屏蔽F12审查元素,禁止使用右键菜单
    c# excel如何导入到sqlserver数据库
  • 原文地址:https://www.cnblogs.com/WR-Eternity/p/10862762.html
Copyright © 2011-2022 走看看