LCT
性质
\(Link\) \(Cut\) \(Tree\) 是基于 \(Splay\) 与实链剖分实现的。
\(LCT\) 可以实现诸多操作:
- 查询、修改链上的信息
- 更换原树的根
- 动态连边、删边
- 动态维护连通性
\(LCT\) 在中序遍历的情况下总是深度严格单调递增的。每个节点仅包含于一个 \(Splay\) 当中。虚边连接两个 \(Splay\),原树中中序遍历靠前的 \(Splay\) 将会作为父节点;实边则连接 \(Splay\) 中的父亲与儿子节点。由虚边连接的两个点,可以从儿子指向父亲,但是父亲无法找到儿子。

如图中的一棵树中,\(Splay\) 结构可能是如下的:

每一个绿圈当中为一棵 \(Splay\)。
函数
\(access\)
\(access(x)\) 即为把根节点与 \(x\) 节点放入一个 \(Splay\) 中,使两点由实边相连。
事实上,只需要四个操作即可完成:
- 将节点转到当前 \(Splay\) 的根
- 换儿子
- 更新信息
- 当前操作点转为轻边指向的父节点,在此重复 \(1\) 至 \(3\) 操作。
inline void access(int x){
for(register int y = 0; x; x = fa[y=x]){ //记录下原节点
splay(x); //x转到根节点
son[x][1] = y; //x的右儿子指向原来的当前点
pushup(x); //更新
}
}
\(makeroot\)
\(makeroot(x)\) 即将 \(x\) 转至根节点,且使左子树为空。
-
\(access(x)\),在此之后 \(x\) 一定是 \(Splay\) 中最深的节点。
-
\(Splay(x)\),无疑所有节点都会转到左子树上。
-
\(pushreverse(x)\),交换左右子树,打上懒惰标记
inline void makeroot(int x){
access(x); //将x与根节点连通
splay(x); //转动使x到根,此时右子树为空
pushreverse(x); //交换左右子树
}
\(findroot\)
\(findroot(x)\) 是查找 \(x\) 在当前 \(Splay\) 中真实的根节点。
- 将 \(Splay\) 转换为没有右子树,\(x\) 为根节点。
- 搜索 \(x\) 的左儿子。此时因为 \(Splay\) 中序遍历左儿子深度总是更浅的,所以我们可以直接一直向下搜左儿子直到没有为止,最终的 \(x\) 即为根节点。
最后再 \(splay(x)\) 保证复杂度。
int findroot(int x){
access(x); //将x与根节点连通
splay(x); //转动使x到根,此时右子树为空
while(son[x][0]) pushdown(x), x = son[x][0]; //向下搜索左子树直至没有
splay(x); //保证时间复杂度
return x;
}
\(split\)
\(split(x, y)\) 可以创建 \(x\) 到 \(y\) 之间的通路且记录下区间。
- 首先将 \(x\) 转至根
- 构建 \(x\) 到 \(y\) 的通路
- 将\(y\)转至根节点,此时 \(sum[y]\) 中记录的就是答案
inline void split(int x, int y){
makeroot(x); //将x转至根
access(y); //构建x到y的通路
splay(y); //将y转至根
}
\(link\)
\(link(x,y)\) 可判断在 \(x\) 与 \(y\) 之间加边是否合法后加边
- 将 \(x\) 转到根
- 然后判断 \(y\) 所在的 \(Splay\) 是否在此根节点是 \(x\),若不是则加边合法,\(fa[x]=y\)。
inline void link(int x, int y){
makeroot(x); //将x转到根
if(findroot(y) != x) fa[x] = y; //若y不在这个splay中即可加边
}
\(cut\)
\(cut(x,y)\) 可判断在 \(x\) 与 \(y\) 之间删边是否合法后删边
- 将 \(x\) 转至根节点
- 判断是否合法
- \(y\) 在原树中父节点是 \(x\)
- \(y\) 在 \(Splay\) 中父节点是 \(x\)
- \(y\) 的左儿子为空。因为 \(makeroot\) 以后 \(x\) 已经只有右子树了,若\(y\)含有左子树则意味着 \(y\) 的左子树会位于 \(x\) 与 \(y\) 之间,\(x\) 与 \(y\) 没有直接相连。
- \(y\) 的父亲节点指向 \(x\) 的右儿子节点
- 更新信息
inline void cut(int x, int y){
makeroot(x); //将x转到根
if(findroot(y) == x && fa[y] == x && !son[y][0]){ //若符合条件
fa[y] = son[x][1] = 0; //y的父亲节点直接指向x的右子树
pushup(x); //更新信息
}
}
例题1:动态树(Link Cut Tree)
\(Code\)
#include<bits/stdc++.h>
using namespace std;
int n, m;
int op, x, y;
int a[300005];
int fa[300005], son[300005][2], sumxor[300005], st[300005];
bool lazy[300005];
template<typename T>
inline void read(T&x){
x = 0; char q; bool f = 1;
while(!isdigit(q = getchar())) if(q == '-') f = 0;
while(isdigit(q)){
x = (x<<1) + (x<<3) + (q^48);
q = getchar();
}
x = f? x:-x;
}
template<typename T>
inline void write(T x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9) write(x/10);
putchar(x%10^48);
return;
}
inline bool nroot(int x){ //判断x与fa[x]是否在同一splay中
return son[fa[x]][0] == x || son[fa[x]][1] == x;
}
inline void pushup(int x){
sumxor[x] = sumxor[son[x][0]] ^ sumxor[son[x][1]] ^ a[x]; //向上更新sumxor值
}
inline void pushreverse(int x){
swap(son[x][0], son[x][1]); //交换son[x][0]与son[x][1]
lazy[x] ^= 1; //lazy更新
}
inline void pushdown(int x){
if(lazy[x]){
if(son[x][0]) pushreverse(son[x][0]); //如果son[x][0]存在则下放son[x][0]
if(son[x][1]) pushreverse(son[x][1]); //如果son[x][1]存在则下放son[x][1]
lazy[x] = 0; //标记为0
}
}
inline void rotate(int x){ //把x向上旋转
int y = fa[x], k = (son[y][1] == x), w = son[x][!k], z = fa[y]; //z是y的父节点,y是x的父节点,k表示x是y的左儿子还是右儿子,w是x的与y对x反向的儿子
if(nroot(y)) son[z][son[z][1] == y] = x; //若y与z是否在一个splay中,z的y儿子变换为x
son[x][!k] = y; //x与y的x儿子反向的那一边儿子为y
son[y][k] = w; //y的与x同向那一边儿子为x
if(w) fa[w] = y; //若存在儿子w,则w的父亲为y
fa[y] = x; //y的父亲更新为x
fa[x] = z; //x的父亲更新为z
pushup(y); //上传
}
inline void splay(int x){ //把x旋转至splay的根节点
int y = x, z, cnt = 0;
st[++cnt] = y; //存放x到根的路径
while(nroot(y)) st[++cnt] = y = fa[y];
while(cnt) pushdown(st[cnt--]); //先下放操作
while(nroot(x)){ //若x不是根节点
y = fa[x]; z = fa[y]; //y是x的父节点,z是y的父节点
if(nroot(y))
rotate((son[y][0]==x)^(son[z][0]==y)?x:y); //若z的y儿子与y的x儿子同向则旋转x,否则旋转y,只有这样才能改变结构
rotate(x); //旋转x
}
pushup(x); //更新数据
}
inline void access(int x){ //根节点与x节点放入一个Splay中,使两点由实边相连
for(register int y = 0; x; x = fa[y=x]){ //记录下原节点
splay(x); //x转到根节点
son[x][1] = y; //x的右儿子指向原来的点
pushup(x); //更新
}
}
inline void makeroot(int x){ //将x转至原树的根节点,且使左子树为空
access(x); //将x与根节点连通
splay(x); //转动使x到根,此时右子树为空
pushreverse(x); //交换左右子树
}
int findroot(int x){ //查找x在当前Splay中真实的根节点
access(x); //将x与根节点连通
splay(x); //转动使x到根,此时右子树为空
while(son[x][0]) pushdown(x), x = son[x][0]; //向下搜索左子树直至没有
splay(x); //保证时间复杂度
return x;
}
inline void split(int x, int y){ //找到x到y之间的通路且记录下区间
makeroot(x); //将x转至根
access(y); //构建x到y的通路
splay(y); //将y转至根
}
inline void link(int x, int y){ //判断在x与y之间加边是否合法后加边
makeroot(x); //将x转到根
if(findroot(y) != x) fa[x] = y; //若y不在这个splay中即可加边
}
inline void cut(int x, int y){ //判断在x与y之间删边是否合法后删边
makeroot(x); //将x转到根
if(findroot(y) == x && fa[y] == x && !son[y][0]){ //若符合条件
fa[y] = son[x][1] = 0; //y的父亲节点直接指向x的右子树
pushup(x); //更新信息
}
}
int main(){
read(n), read(m);
for(register int i = 1; i <= n; ++i) read(a[i]);
while(m--){
read(op), read(x), read(y);
if(op == 0){
split(x, y); //创立通路并统计
write(sumxor[y]); //输出值
putchar('\n');
}
if(op == 1) link(x, y); //连接
if(op == 2) cut(x, y); //删除
if(op == 3){
splay(x); //将x转到根节点则修改后不会影响
a[x] = y; //修改
}
}
return 0;
}
例题二:[国家集训队]Tree II
思路
\(LCT\) 版的线段树 \(2\),一个道理,先维护乘法再维护加法最后维护旋转标记即可。
\(Code\)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 51061;
int n, m, x, y, c;
char op;
int size[100005], v[100005], mlz[100005], plz[100005], rlz[100005], fa[100005], son[100005][2], sum[100005];
int st[100005];
template<typename T>
inline void read(T&x){
x = 0; char q; bool f = 1;
while(!isdigit(q = getchar())) if(q == '-') f = 0;
while(isdigit(q)){
x = (x<<1) + (x<<3) + (q^48);
q = getchar();
}
x = f? x:-x;
}
template<typename T>
inline void write(T x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9) write(x/10);
putchar(x%10^48);
}
inline bool nroot(int u){
return u == son[fa[u]][1] || u == son[fa[u]][0];
}
inline void pushadd(int u, int num){
sum[u] = (sum[u] + size[u]*num) % mod;
v[u] = (v[u] + num) % mod;
plz[u] = (plz[u] + num) % mod;
}
inline void pushmul(int u, int num){
sum[u] = (sum[u] * num) % mod;
v[u] = (v[u] * num) % mod;
plz[u] = (plz[u] * num) % mod;
mlz[u] = (mlz[u] * num) % mod;
}
inline void pushreverse(int u){
swap(son[u][0], son[u][1]);
rlz[u] ^= 1;
}
inline void pushdown(int u){
if(mlz[u] != 1){
pushmul(son[u][0], mlz[u]), pushmul(son[u][1], mlz[u]);
mlz[u] = 1;
}
if(plz[u]){
pushadd(son[u][0], plz[u]), pushadd(son[u][1], plz[u]);
plz[u] = 0;
}
if(rlz[u]){
if(son[u][0]) pushreverse(son[u][0]);
if(son[u][1]) pushreverse(son[u][1]);
rlz[u] = 0;
}
}
inline void pushup(int u){
sum[u] = (sum[son[u][1]] + sum[son[u][0]] + v[u]) % mod;
size[u] = size[son[u][0]] + size[son[u][1]] + 1;
}
inline void rotate(int u){
int v = fa[u], p = fa[v], k = son[v][1] == u, w = son[u][!k];
if(nroot(v)) son[p][son[p][1] == v] = u;
son[u][!k] = v;
son[v][k] = w;
if(w) fa[w] = v;
fa[v] = u;
fa[u] = p;
pushup(v);
}
inline void splay(int u){
int v = u, cnt = 0, p;
st[++cnt] = v;
while(nroot(v)) st[++cnt] = v = fa[v];
while(cnt) pushdown(st[cnt--]);
while(nroot(u)){
v = fa[u], p = fa[v];
if(nroot(v)) rotate((son[p][1] == v) ^ (son[v][1] == u)? u:v);
rotate(u);
}
pushup(u);
}
inline void access(int u){
for(register int v = 0; u; u = fa[v = u]){
splay(u);
son[u][1] = v;
pushup(u);
}
}
inline void makeroot(int u){
access(u);
splay(u);
pushreverse(u);
}
inline void split(int u, int v){
makeroot(u);
access(v);
splay(v);
}
inline void link(int u, int v){
makeroot(u);
fa[u] = v;
}
inline void cut(int u, int v){
split(x, y);
fa[u] = son[v][0] = 0;
}
inline void add(int u, int v, int num){
split(u, v);
pushadd(v, num);
}
inline void mul(int u, int v, int num){
split(u, v);
pushmul(v, num);
}
signed main(){
read(n), read(m);
for(register int i = 1; i <= n; ++i) v[i] = size[i] = mlz[i] = 1;
for(register int i = 1; i < n; ++i){
read(x), read(y);
link(x, y);
}
while(m--){
op = getchar();
if(op == '+'){
read(x), read(y), read(c);
add(x, y, c);
}
if(op == '-'){
read(x), read(y);
cut(x, y);
read(x), read(y);
link(x, y);
}
if(op == '*'){
read(x), read(y), read(c);
mul(x, y, c);
}
if(op == '/'){
read(x), read(y);
split(x, y);
write(sum[y]);
putchar('\n');
}
}
return 0;
}

浙公网安备 33010602011771号