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 个函数而已
- \(access(x)\), 将根节点到 \(x\) 的路径变成实边,将其他的与其矛盾的边变成虚边
- 实现:先将 \(x\) 设为这的 \(splay\) 的根节点,然后找到它这棵 \(splay\) 的父节点 不妨设为 \(y\),将 \(y\) 设为 \(y\) 所在实链的 \(splay\) 的根节点,再将 \(x\) 设为 \(y\) 的后继就好了(这只要将 \(x\) 的 \(splay\) 插到 \(y\) 的右子树即可), 此时这条虚边已经变成了实边,\(splay\) 的根节点就是 \(y\),以此类推,这条链就维护好了
- \(makeroot(x)\) 将 \(x\) 设为根节点,还会将 \(x\) 设为 \(x\) 所在的实链的 \(splay\) 的根节点。
- 实现先建立一条 \(x\) 向根节点的实路径,再将 \(x\) 设为这条实边的 \(splay\) 的根节点,再将 \((root,x)\) 翻转。
- \(findroot(x)\) 找到 \(x\) 所在树的根节点,还会将 \(y\) 所在树的根节点旋转到 \(y\) 所在 \(splay\) 的根节点。
- 实现:先将 \(x\) 向 \(root\) 的路径设为实路径,再将 \(x\) 设为 \(root\),然后根节点不断往左走直到不能走,这就是之前的根节点
- \(spilt(x,y)\):将 \(x\) 到 \(y\) 的路径设为实边路径
- 实现:将 \(x\) 变成根节点,然后将 \(y\) 向根节点的路径设为实边
- \(link(x,y)\):如果 \(x,y\) 不连通,则加入 \(x,y\) 这条边
- 实现:是否连通:将 \(x\) 设为这棵树的根节点,再 \(findroot(y)\) 看一下是不是 \(x\) 即可;连边:因为 \(makeroot(x)\) 有一个附属的功能:将 \(x\) 所在的实链的 \(splay\) 的根节点设为 \(x\),所以只要让 \(x\) 的父节点设为 \(y\) 即可。
- \(cnt(x,y)\) 如果 \(x, y\) 连通,就将 \((x,y)\) 删除
- 实现:是否连通:将 \(x\) 设为这棵树的根节点,再 \(findroot(y)\) 是不是 \(y\),并且 \(y\) 是 \(x\) 的后继 ;删边:将 \(x\) 的后继设为空即可
- \(isroot(x)\) 判断 \(x\) 是否是 \(x\) 所在的 \(splay\) 的根节点
- 实现:其实就是看一下 \(x\) 有没有父亲即可,如果有,那 \(x\) 一定是他父亲的左儿子,或者是右儿子,如果都不是,它就一定是这棵 \(splay\) 的根节点
- 代码
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;
}

浙公网安备 33010602011771号