Fork me on GitHub

Link-Cut-Tree 学习笔记

LCT学习笔记

和树链剖分比较相似,但是它的时间复杂度是 \(O(nlogn)\), 树链剖分是 \(O(nlogn^2)\)

维护一个森林,可以支持以下操作

  • 求联通两点 \(x\), \(y\) 的路径上的所有点的某一求值(eg.\(xor\))
  • 将不联通的 \(x\), \(y\) 之间增加一条边
  • 删除存在的 \((x,y)\) 这条边
  • \(x\) 的权值改为 \(w\)

定义

每个点最多只有一条边是连向儿子的实边,剩下的是虚边。

维护

  • \(splay\) 维护所有的实边路径,用 \(splay\) 中的后继和前驱来维护原树中的父子关系
  • \(splay\) 的根节点来维护虚边

判断是否是虚边,就看自己的 \(splay\) 有没有指向父亲所在的 \(splay\) 即可,就是如果 \(u\) 的前驱是\(fa_u\), 但 \(fa_u\) 的后继不是 \(u\), 则 \((u, fa_u)\) 就是虚边,反之就是实边。

操作

就是只有 7 个函数而已

  1. \(access(x)\), 将根节点到 \(x\) 的路径变成实边,将其他的与其矛盾的边变成虚边
  2. 实现:先将 \(x\) 设为这的 \(splay\) 的根节点,然后找到它这棵 \(splay\) 的父节点 不妨设为 \(y\),将 \(y\) 设为 \(y\) 所在实链的 \(splay\) 的根节点,再将 \(x\) 设为 \(y\) 的后继就好了(这只要将 \(x\)\(splay\) 插到 \(y\) 的右子树即可), 此时这条虚边已经变成了实边,\(splay\) 的根节点就是 \(y\),以此类推,这条链就维护好了
  3. \(makeroot(x)\)\(x\) 设为根节点,还会将 \(x\) 设为 \(x\) 所在的实链的 \(splay\) 的根节点。
  4. 实现先建立一条 \(x\) 向根节点的实路径,再将 \(x\) 设为这条实边的 \(splay\) 的根节点,再将 \((root,x)\) 翻转。
  5. \(findroot(x)\) 找到 \(x\) 所在树的根节点,还会将 \(y\) 所在树的根节点旋转到 \(y\) 所在 \(splay\) 的根节点。
  6. 实现:先将 \(x\)\(root\) 的路径设为实路径,再将 \(x\) 设为 \(root\),然后根节点不断往左走直到不能走,这就是之前的根节点
  7. \(spilt(x,y)\):将 \(x\)\(y\) 的路径设为实边路径
  8. 实现:将 \(x\) 变成根节点,然后将 \(y\) 向根节点的路径设为实边
  9. \(link(x,y)\):如果 \(x,y\) 不连通,则加入 \(x,y\) 这条边
  10. 实现:是否连通:将 \(x\) 设为这棵树的根节点,再 \(findroot(y)\) 看一下是不是 \(x\) 即可;连边:因为 \(makeroot(x)\) 有一个附属的功能:将 \(x\) 所在的实链的 \(splay\) 的根节点设为 \(x\),所以只要让 \(x\) 的父节点设为 \(y\) 即可。
  11. \(cnt(x,y)\) 如果 \(x, y\) 连通,就将 \((x,y)\) 删除
  12. 实现:是否连通:将 \(x\) 设为这棵树的根节点,再 \(findroot(y)\) 是不是 \(y\),并且 \(y\)\(x\) 的后继 ;删边:将 \(x\) 的后继设为空即可
  13. \(isroot(x)\) 判断 \(x\) 是否是 \(x\) 所在的 \(splay\) 的根节点
  14. 实现:其实就是看一下 \(x\) 有没有父亲即可,如果有,那 \(x\) 一定是他父亲的左儿子,或者是右儿子,如果都不是,它就一定是这棵 \(splay\) 的根节点
  15. 代码
void access(int x) 
{
    int z = x;
    for (int y = 0; x; y = x, x = tr[x].p)
    {
        splay(x);
        tr[x].s[1] = y, pushup(x);
    }
    splay(z);
}

void makeroot(int x) 
{
    access(x);
    pushrev(x);
}

int findroot(int x) 
{
    access(x);
    while (tr[x].s[0]) pushdown(x), x = tr[x].s[0];
    splay(x);
    return x;
}

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

void link(int x, int y)
{
    makeroot(x);
    if (findroot(y) != x) tr[x].p = y;
}

void cut(int x, int y)
{
    makeroot(x);
    if (findroot(y) == x && tr[y].p == x && !tr[y].s[0])
    {
        tr[x].s[1] = tr[y].p = 0;
        pushup(x);
    }
}

维护信息就用 \(splay\) 来维护就好了!

\(xor\) 为例, \(splay\) 函数如下:

void pushrev(int x)
{
    swap(tr[x].s[0], tr[x].s[1]);
    tr[x].rev ^= 1;
}

void pushup(int x)
{
    tr[x].sum = tr[tr[x].s[0]].sum ^ tr[x].v ^ tr[tr[x].s[1]].sum;
}

void pushdown(int x)
{
    if (tr[x].rev)
    {
        pushrev(tr[x].s[0]), pushrev(tr[x].s[1]);
        tr[x].rev = 0;
    }
}

void rotate(int x)
{
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    if (!isroot(y)) tr[z].s[tr[z].s[1] == y] = x;
    tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}

void splay(int x)
{
    int top = 0, r = x;
    stk[ ++ top] = r;
    while (!isroot(r)) stk[ ++ top] = r = tr[r].p;
    while (top) pushdown(stk[top -- ]);
    while (!isroot(x))
    {
        int y = tr[x].p, z = tr[y].p;
        if (!isroot(y))
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }
}

例题:
2539. 动态树

按照上面的分析,就可以做了,代码如下:

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

int n, m;
struct Node
{
    int s[2], p, v;
    int sum, rev;
}tr[N];
int stk[N];

void pushrev(int x)
{
    swap(tr[x].s[0], tr[x].s[1]);
    tr[x].rev ^= 1;
}

void pushup(int x)
{
    tr[x].sum = tr[tr[x].s[0]].sum ^ tr[x].v ^ tr[tr[x].s[1]].sum;
}

void pushdown(int x)
{
    if (tr[x].rev)
    {
        pushrev(tr[x].s[0]), pushrev(tr[x].s[1]);
        tr[x].rev = 0;
    }
}

bool isroot(int x)
{
    return tr[tr[x].p].s[0] != x && tr[tr[x].p].s[1] != x;
}

void rotate(int x)
{
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    if (!isroot(y)) tr[z].s[tr[z].s[1] == y] = x;
    tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}

void splay(int x)
{
    int top = 0, r = x;
    stk[ ++ top] = r;
    while(!isroot(r)) stk[ ++ top] = r = tr[r].p;
    while(top) pushdown(stk[top -- ]);
    while(!isroot(x))
    {
        int y = tr[x].p, z = tr[y].p;
        if (!isroot(y))
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }
}

void access(int x) 
{
    int z = x;
    for(int y = 0 ; x ; y = x, x = tr[x].p)
    {
        splay(x);
        tr[x].s[1] = y;
        pushup(x);
    }
    splay(z);
}

void makeroot(int x)
{
    access(x);
    pushrev(x);
}

int findroot(int x)
{
    access(x);
    while (tr[x].s[0]) pushdown(x), x = tr[x].s[0];
    splay(x);
    return x;
}

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

void link(int x, int y) 
{
    makeroot(x);
    if (findroot(y) != x) tr[x].p = y;
}

void cut(int x, int y) 
{
    makeroot(x);
    if (findroot(y) == x && tr[y].p == x && !tr[y].s[0])
    {
        tr[x].s[1] = tr[y].p = 0;
        pushup(x);
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++ ) scanf("%d", &tr[i].v);
    while(m -- )
    {
        int t, x, y;
        scanf("%d%d%d", &t, &x, &y);
        if(t == 0)
        {
            split(x, y);
            printf("%d\n", tr[y].sum);
        }
        else if(t == 1) link(x, y);
        else if(t == 2) cut(x, y);
        else
        {
            splay(x);
            tr[x].v = y;
            pushup(x);
        }
    }

    return 0;
}

题单

LCT

posted @ 2025-06-04 15:00  tony0530  阅读(44)  评论(0)    收藏  举报