• Link Cut Tree学习笔记


    捋一下思路

    模板题:https://www.luogu.org/problemnew/show/P3690

    推荐LCT的教程,个人认为很详细,本文做了部分引用:https://www.luogu.org/blog/flashblog/solution-p3690

    前置知识:Splay

    LCT是一种动态树,支持连边和断边,还有树上路径的查询

    LCT似乎是用Splay维护深度,这点知道了会感觉好理解一些

    重要的操作有:

    1. access(x),将x到根节点的路径上的边都变成重边。循环处理,只有四步——转到根;换儿子;更新信息;当前操作点切换为轻边所指的父亲,转第一步

    2. makeroot(x),将x变为所在Splay的根节点。access操作后x变成Splay内深度最大的点,然后像Splay一样把x转上去

    3. findroot(x),找所在树的原根。不停找左儿子,因为左儿子维护的深度比当前节点小。

    4. split操作。把一个点转到根节点,原路径就是另一个点到根节点的路径。然后把下面的点转上去更新答案。

    5. link(x, y),使x的父亲指向y,连一条轻边。如果在同一棵树则不连边

    6. cut(x, y),使x和y断边。使x为根后,y的父亲一定指向x,深度相差一定是1。当access(y),splay(y)以后,x一定是y的左儿子,直接双向断开连接。如果不一定存在该边,先判一下连通性(注意findroot(y)以后x成了根),再看看x,y是否有父子关系,还要看y是否有左儿子(因为也可能y的父亲还是x,那么其它的点就在y的左子树中)。

    #include <bits/stdc++.h>
    #define root 0
    
    using namespace std;
    
    const int maxn = 300010;
    
    struct LCT { // 其实struct Splay会更好QAQ
    	int son[2], fa, val, s;
    	bool tag;
    }t[maxn]; 
    
    int st[maxn];
    
    bool isroot(int x) {
    	return t[t[x].fa].son[0] == x || t[t[x].fa].son[1] == x;
    } // 判断节点是否是一颗Splay的根,如果是的话返回值是false,不是返回值是true(其实改成not_root会更好一些QAQ)
    
    void pushup(int x) {
    	t[x].s = t[t[x].son[0]].s ^ t[t[x].son[1]].s ^ t[x].val;
    }
    
    void reverse(int x) {
    	swap(t[x].son[0], t[x].son[1]);
    	t[x].tag ^= 1;
    }
    
    void pushdown(int x) {
    	if(t[x].tag) {
    		if(t[x].son[0]) reverse(t[x].son[0]);
    		if(t[x].son[1]) reverse(t[x].son[1]);
    		t[x].tag = 0;
    	}
    }
    
    void rotate(int x) {
    	int fa = t[x].fa, grandfa = t[t[x].fa].fa, s = t[fa].son[1] == x, tmp = t[x].son[s ^ 1];
    	if(isroot(fa)) {
    		t[grandfa].son[t[grandfa].son[1] == fa] = x;
    	}
    	t[x].son[s ^ 1] = fa;
    	t[fa].son[s] = tmp;
    	if(tmp) {
    		t[tmp].fa = fa;
    	}
    	t[fa].fa = x;
    	t[x].fa = grandfa;
    	pushup(fa);
    }
    
    void pushdown_all(int x) {
    	if(isroot(x)) pushdown_all(t[x].fa);
    	pushdown(x);
    }
    
    void splay(int x) {
    	int fa, grandfa;
    	pushdown_all(x);
    	while(isroot(x)) {
    		fa = t[x].fa, grandfa = t[fa].fa;
    		if(isroot(fa)) rotate((t[fa].son[0] == x) ^ (t[grandfa].son[0] == fa) ? x : fa);
    		rotate(x);
    		// cout << x << " " << fa << " " << grandfa << endl;
    	}
    	pushup(x);
    } // 以上是splay部分
    

    下面是LCT部分:

    inline void access(int x) {
    	for(register int y = root; x; y = x, x = t[x].fa) {
    		// cout << "QAQ" << endl;
    		splay(x);
    		t[x].son[1] = y;
    		pushup(x);
    		// cout << "QAQ" << endl;
    	}
    } // 将该节点到该节点所在Splay的根节点上的所有路径都变为重边
    
    inline void makeroot(int x) {
    	access(x); splay(x);
    	reverse(x);
    } // 将一个节点变成所在Splay的根
    
    inline int findroot(int x) {
    	access(x); splay(x);
    	while(t[x].son[0]) {
    		pushdown(x);
    		x = t[x].son[0];
    	}
    	splay(x);
    	return x;
    } // 找到所在Splay的根节点
    
    inline void split(int x, int y) {
    	makeroot(x);
    	access(y); 
    	splay(y);
    } // 分离出x到y的路径
    
    inline void link(int x, int y) {
    	makeroot(x);
    	if(findroot(y) == x) return ;
    	t[x].fa = y;
    } // 连边
    
    inline void cut(int x, int y) {
    	makeroot(x);
    	if(findroot(y) != x || t[y].fa != x || t[y].son[0]) return ;
    	t[y].fa = t[x].son[1] = 0;
    	pushup(x);
    } // 断边
    

    主程序:

    int main() {
    	int n = read(), m = read();
    	for(register int i = 1; i <= n; i++) {
    		t[i].val = read();
    	}
    	while(m--) {
    		int opt = read(), x = read(), y = read();
    		if(!opt) {
    			split(x, y);
    			write(t[y].s), putchar(10);
    		}
    		if(opt == 1) {
    			link(x, y);
    		}
    		if(opt == 2) {
    			cut(x, y);
    		}
    		if(opt == 3) {
    			splay(x);
    			t[x].val = y;
    		}
    	}
        return 0;
    }
    
  • 相关阅读:
    mysql安装脚本
    vim常用命令
    CentOS 6.5使用国内网易163的源
    053(七十五)
    053(七十四)
    053(七十三)
    053(七十二)
    053(七十一)
    053(七十)
    053(六十九)
  • 原文地址:https://www.cnblogs.com/iycc/p/10440647.html
Copyright © 2020-2023  润新知