• 动态树Link-cut tree(LCT)总结


    动态树是个好玩的东西

    LCT题集

    预备知识

    Splay

    树链剖分(好像关系并不大)

    先搬dalao博客

    • 什么是LCT?
      动态树是一类要求维护森林的连通性的题的总称,这类问题要求维护某个点到根的某些数据,支持树的切分,合并,以及对子树的某些操作。其中解决这一问题的某些简化版(不包括对子树的操作)的基础数据结构就是LCT(link-cut tree)。
      LCT的大体思想类似于树链剖分中的轻重链剖分,轻重链剖分是处理出重链来,由于重链的定义和树链剖分是处理静态树所限,重链不会变化,变化的只是重链上的边或点的权值,由于这个性质,我们用线段树来维护树链剖分中的重链,但是LCT解决的是动态树问题(包含静态树),所以需要用更灵活的Splay来维护这里的“重链”

    讲了这么多,其实就是维护森林的树链剖分,每一条链用Splay维护

    LCT的主要性质

    1. 每一个Splay维护的是一条从上到下按在原树中深度严格递增的路径,且中序遍历Splay得到的每个点的深度序列严格递增。
      比如有一棵树,根节点为1(深度1),有两个儿子2,3(深度2),那么Splay有3种构成方式:
      {1−2},{3}
      {1−3},{2}
      {1},{2},{3}(每个集合表示一个Splay)
      而不能把1,2,3同放在一个Splay中(存在深度相同的点)

    2. 每个节点包含且仅包含于一个Splay中

    3. 边分为实边和虚边,实边包含在Splay中,而虚边总是由一棵Splay指向另一个节点(指向该Splay中中序遍历最靠前的点在原树中的父亲)。
      因为性质2,当某点在原树中有多个儿子时,只能向其中一个儿子拉一条实链(只认一个儿子),而其它儿子是不能在这个Splay中的。
      那么为了保持树的形状,我们要让到其它儿子的边变为虚边,由对应儿子所属的Splay的根节点的父亲指向该点,而从该点并不能直接访问该儿子(认父不认子)。

    各种操作

    access(x)

    打通(x)到动态树的根的路径(使路径上的所有点在同一个Splay中)

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

    makeroot(x)

    使(x)变成动态树的根

    void makeroot(int x) {
    	access(x); splay(x);putrev(x);
    	return ;
    }
    

    findroot(x)

    用来判断连通性(类似并查集)(findroot(x)==findroot(y)表明x,y在同一棵树中)

    inline int findroot(int x) {
    	access(x); splay(x);
    	while (t[x].ch[0]) pushdown(x), x = t[x].ch[0];
    	return x;
    }
    

    split(x,y)

    打通(x)(y)的路径(类似access)

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

    link(x,y)

    连接(x-y)

    void link(int x, int y) {
    	makeroot(x);
    	if (findroot(y) == x) return ;//已经连通
    	t[x].fa = y;//如果写成t[y].fa = x, y与原先的树就断开,就不会与其连通
    	return ;
    }
    

    cut(x, y)

    断开(x-y)

    void cut(int x, int y) {
    	makeroot(x);
    	if (findroot(y) == x && t[x].fa == y && !t[x].ch[1])//满足它们直接连接才断开
    		t[x].fa = t[y].ch[0] = 0, pushup(y);
    	return ;
    }
    

    例题

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

    Code

    #include<bits/stdc++.h>
    
    #define LL long long
    #define RG register
    const int N = 300010;
    using namespace std;
    
    inline int gi() {
    	RG int x = 0; RG char c = getchar(); bool f = 0;
    	while (c != '-' && (c < '0' || c > '9')) c = getchar();
    	if (c == '-') c = getchar(), f = 1;
    	while (c >= '0' && c <= '9') x = x*10+c-'0', c = getchar();
    	return f ? -x : x;
    }
    
    struct node {
    	int v, s, fa, ch[2];
    	bool rev;
    }t[N];
    int S[N], top;
    void putrev(int x) {
    	swap(t[x].ch[0], t[x].ch[1]);
    	t[x].rev ^= 1;
    	return ;
    }
    #define pushup(x) (t[x].s = (t[x].v^t[t[x].ch[0]].s^t[t[x].ch[1]].s))
    void pushdown(int x) {
    	if (t[x].rev) {
    		if (t[x].ch[0]) putrev(t[x].ch[0]);
    		if (t[x].ch[1]) putrev(t[x].ch[1]);
    		t[x].rev = 0;
    	}
    	return ;
    }
    #define get(x) (t[t[x].fa].ch[1]==x)
    bool isroot(int x) {
    	return (t[t[x].fa].ch[0] != x) && (t[t[x].fa].ch[1] != x);
    }
    void rotate(int x) {
    	int k = get(x), y = t[x].fa, z = t[y].fa;
    	if (!isroot(y)) t[z].ch[get(y)] = x;
    	t[x].fa = z;
    	t[t[x].ch[k^1]].fa = y, t[y].ch[k] = t[x].ch[k^1];
    	t[y].fa = x, t[x].ch[k^1] = y;
    	pushup(y);
    	return ;
    }
    void splay(int x) {
    	S[top = 1] = x;
    	for (RG int i = x; !isroot(i); i = t[i].fa) S[++top] = t[i].fa;
    	for (RG int i = top; i; i--) pushdown(S[i]);
    	while (!isroot(x)) {
    		int y = t[x].fa;
    		if (!isroot(y))
    			(get(x) ^ get(y)) ? rotate(x) : rotate(y);
    		rotate(x);
    	}
    	pushup(x);
    	return ;
    }
    void access(int x) {
    	for (int y = 0; x; y = x, x = t[x].fa)
    		splay(x), t[x].ch[1] = y, pushup(x);
    	return ;
    }
    void makeroot(int x) {
    	access(x); splay(x);putrev(x);
    	return ;
    }
    inline int findroot(int x) {
    	access(x); splay(x);
    	while (t[x].ch[0]) pushdown(x), x = t[x].ch[0];
    	return x;
    }
    void link(int x, int y) {
    	makeroot(x);
    	if (findroot(y) == x) return ;
    	t[x].fa = y;
    	return ;
    }
    void split(int x, int y) {
    	makeroot(x), access(y), splay(y);
    	return ;
    }
    void cut(int x, int y) {
    	makeroot(x);
    	if (findroot(y) == x && t[x].fa == y && !t[x].ch[1])
    		t[x].fa = t[y].ch[0] = 0, pushup(y);
    	return ;
    }
    
    
    int main() {
    	int n = gi(), T = gi();
    	for (RG int i = 1; i <= n; i++) t[i].v = gi();
    	while (T--) {
    		int op = gi(), x = gi(), y = gi();
    		if (!op) {
    			split(x, y);
    			printf("%d
    ", t[y].s);
    		}
    		else if (op == 1) link(x, y);
    		else if (op == 2) cut(x, y);
    		else {
    			access(x); splay(x); t[x].v = y; pushup(x);
    		}
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    抽取一个简单的按钮方法
    一些iOS笔试题目
    使用第三方框架 Masonry 实现自动布局
    AutoLayout适配
    iOS面试小题集锦
    大牛们的技术博客
    5、过滤流
    3、过滤文件夹
    1、File类简介
    贪婪模式和非贪婪模式
  • 原文地址:https://www.cnblogs.com/zzy2005/p/10312977.html
Copyright © 2020-2023  润新知