zoukankan      html  css  js  c++  java
  • LCT

    今天,YCC 本来是想搞一搞数论,但是老吕说:今天我闲着,给你们讲一讲LCT。

    LCT 是什么?

    是一个由若干棵子结点无序的有根树组成的森林,支持对树的分割, 合并, 对某个点到它的根的路径的某些操作, 以及对某个点的子树进行的某些操作。

    基本概念

    原树:就是原来的树,实孩子,实边,实链,都是原树中的。

    辅助树:是很多splay 构成的树,程序中真正操作的都是辅助树。

    实孩子:这个实孩子与轻重链剖分中的重孩子还不同。

    轻重链剖分中的重孩子是根据子树的大小来确定的,但是LCT中的实孩子是不确定的,当我们需要它时,就将它从虚孩子变为实孩子。

    实边:连接实孩子的边称为实边。

    实链:实边组成的的链为实链。

    基本思想

    思想和树链剖分的思想类似,每个点(叶子点除外)中最多有一个孩子为实孩子(可能没有)。连接实孩子的边称为实边,实边组成的的链为实链。

    把实链交给splay维护。按深度作为关键字,每一个 splay 的中序遍历就是在原树中从上往下的一条链。

    这样还剩下一些虚孩子,和各个分散的 splay。 让每棵 splay 的根节点的父亲节点指向原树中这条链 的父亲节点(即实链链顶的父亲节点)。

    这类连边与通常 Splay 中(也就是原树上的实边)的连边有所不同,区别在于这儿子认父亲,而父亲不认儿子,对应着原树的一条 虚边

    基础操作

    首先是 splay 和 rotate。

    void splay(int p) 
    {
    	Push_down(p);
    	for(; !isroot(p); rotate(p)) 
    		if(!isroot(fa(p))) rotate(get(fa(p)) == get(p) ? fa(p) : p);
    }
    
    void rotate(int p) 
    {
    	int fath = fa(p), x = get(p);
    	if(!isroot(fa(p))) t[fa(fa(p))].son[get(fa(p))] = p;
    	fa(p) = fa(fa(p));
    	t[fath].son[x] = t[p].son[x ^ 1];
    	fa(t[p].son[x ^ 1]) = fath;
    	t[p].son[x ^ 1] = fath;
    	fa(fath) = p;
    	push_up(fath), push_up(p);
    }
    

    isroot(x):判断x是否是辅助树的根。

    方法:splay 的根结点的父亲并不认这个孩子。原树的根所在的 splay 的父亲点是 (0)

    bool isroot(int x)//判断是否为splay中的根 
    {
    	return ls(fa(x)) != x && rs(fa(x)) != x;//根节点的父亲不认它这个孩子 
    }
    

    access(x):把 (x) 点下面的实边断开,并把 (x)​ 点一路向上边到树的根,(也就是,打通根节点到指定节点的实链,使得一条中序遍历以根开始、以指定点结束的 splay出现。)

    方法:把 (x) 点伸展到 splay 的根,再把它的右子树连到 y,也就是连上了下一层的实链,然后 y 更新为x,而 x 更新为 x 的父亲,继续向上连接。由于 y 的初始值为0,所以第一次连接也就是断开了下面的实边。

    for(int x = 0; p; x = p, p = fa(p)) 
        splay(p), rs(p) = x, push_up(p);
    

    link(x,y):连接两个点。

    方法:把 (x) 点变成所在原树的根,然后把 (x) 点的父亲变成 (y)

    void link(int x, int y) 
    {
    	makeroot(x);
    	if(find(y) != x) fa(x) = y;
    }
    

    cut(x,y):断开两个点间的边。

    void cut(int x, int y) 
    {
    	makeroot(x);
    	if(find(y) != x || fa(y) != x || ls(y)) return ;
    	fa(y) = rs(x) = 0;
    	push_up(x);
    }
    

    makeroot(x):把x点变为树的根。

    方法:首先的把 (x) 点 access 到根,把 (x) 点到根变成一个 splay,然后把 (x) 伸展到根。由于 (x) 点是辅助树在原树中最下面的点,所以这时其它的点都在 (x) 的左子树上,只要把左子树变成右子树,(x) 也就变成了根。

    void makeroot(int p) 
    {
    	access(p); splay(p); f(p);
    }
    

    find(x):查找 (x)​​​​ 所在树的根。

    方法:首先把 (x) 点 access 到原树的根,并把它 splay 到辅助树的根,这时原树的根就是 (x) 左子树中最左侧的点。

    int find(int p) 
    {
    	access(p); splay(p);
    	while(ls(p)) push_down(p), p = ls(p);
    	splay(p);
    	return p;
    }
    

    /

    /*
    Date:2021.9.7
    Source:luogu 3690
    konwledge:
    */
    #include <iostream>
    #include <cstdio>
    #define orz cout << "AK IOI" << "
    "
    #define fa(x) t[x].fa
    #define ls(x) t[x].son[0]
    #define rs(x) t[x].son[1]
    #define get(x) (rs(fa(x)) == x)
    
    using namespace std;
    const int maxn = 3e5 + 10; 
    
    inline int read()
    {
    	int f = 0, x = 0; char ch = getchar();
    	while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    	while(isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar();
    	return f ? -x : x;
    }
    inline void print(int X)
    {
    	if(X < 0) X = ~(X - 1), putchar('-');
    	if(X > 9) print(X / 10);
    	putchar(X % 10 + '0');
    }
    
    int n, m;
    struct node {
    	int son[2], fa, val, sum, lzy;
    } t[maxn];
    int kcnt;
    bool isroot(int x)
    {
    	return ls(fa(x)) != x && rs(fa(x)) != x;
    }
    void push_up(int p)
    {
    	t[p].sum = t[ls(p)].sum ^ t[rs(p)].sum ^ t[p].val;
    }
    void f(int p) 
    {
    	swap(ls(p), rs(p));
    	t[p].lzy ^= 1;
    }
    void push_down(int p) 
    {
    	if(t[p].lzy) f(ls(p)), f(rs(p)), t[p].lzy = 0;
    }
    void rotate(int p) 
    {
    	int fath = fa(p), x = get(p);
    	if(!isroot(fa(p))) t[fa(fa(p))].son[get(fa(p))] = p;
    	fa(p) = fa(fa(p));
    	t[fath].son[x] = t[p].son[x ^ 1];
    	fa(t[p].son[x ^ 1]) = fath;
    	t[p].son[x ^ 1] = fath;
    	fa(fath) = p;
    	push_up(fath), push_up(p);
    }
    void Push_down(int p) 
    {
    	if(!isroot(p)) Push_down(fa(p));
    	push_down(p);
    }
    void splay(int p) 
    {
    	Push_down(p);
    	for(; !isroot(p); rotate(p)) 
    		if(!isroot(fa(p))) rotate(get(fa(p)) == get(p) ? fa(p) : p);
    }
    void access(int p) 
    {
    	for(int x = 0; p; x = p, p = fa(p)) splay(p), rs(p) = x, push_up(p);
    }
    void makeroot(int p) 
    {
    	access(p); splay(p); f(p);
    }
    int find(int p) 
    {
    	access(p); splay(p);
    	while(ls(p)) push_down(p), p = ls(p);
    	splay(p);
    	return p;
    }
    void split(int x, int y) 
    {
    	makeroot(x); access(y); splay(y);
    }
    void link(int x, int y) 
    {
    	makeroot(x);
    	if(find(y) != x) fa(x) = y;
    }
    void cut(int x, int y) 
    {
    	makeroot(x);
    	if(find(y) != x || fa(y) != x || ls(y)) return ;
    	fa(y) = rs(x) = 0;
    	push_up(x);
    }
    
    int main() 
    {
    	n = read(), m = read();
    	for(int i = 1; i <= n; i++) t[i].val = read();
    	for(int i = 1; i <= m; i++) 
    	{
    		int opt = read(), x = read(), y = read();
    		if(opt == 0) split(x, y), print(t[y].sum), puts("");
    		if(opt == 1) link(x, y);
    		if(opt == 2) cut(x, y);
    		if(opt == 3) splay(x), t[x].val = y;
    	}
    	return 0;
    }
    
  • 相关阅读:
    【转】 MySQL高级知识(一)——基础
    inline元素的间距问题
    ES6对于数组的扩展
    JavaScript的垃圾回收机制
    call() apply() bind()
    防抖和节流
    Promise
    js的事件机制
    Javascript异步操作的异常处理
    JavaScript的事件执行机制及异步
  • 原文地址:https://www.cnblogs.com/yangchengcheng/p/15242733.html
Copyright © 2011-2022 走看看