LCT
LCT
前置芝士:
-
链剖分:指一类对树的边进行轻重划分的操作 这样做的目的是为了减少某些链上的修改/查询操作的复杂度
目前有:重链剖分 实链剖分 和并不常见的长链剖分
-
实链剖分:将某一个儿子的连边划分为实边 其他边划分为虚边
区别就在于虚实是动态变化的 因此需要使用splay来维护每一条链
一些定义:
- 实边/虚边:和树剖中的轻重边相似 但注意 对于某一些非叶子节点 可能连边都是虚边
- 实路径:用若干条实边首尾相连构成的路径
- 实链:由实边构成的极大路径
- 实链的父亲:实链中深度最小的点的父亲 也就是原来这条链在原树中的父亲
LCT与树剖本质区别就是:LCT维护的是动态的树 树剖只能维护静态 最多支持修改
应用场景:
- 给出一棵树(或森林),树的形态可变化。
- 查询、修改树上路径的信息(最值,总和等)
- 更换树根
- 动态连边(通过连边合并两棵树)、删边(通过删边将一棵树一分为二)
- 询问某两个节点是否连通
基本结构:
将原树转化为辅助树
- 剖分:将原树剖分成实链和虚边 原树可能是无根树 但我们通常看成有根树来处理
- 转化实链: 以深度从小到大的次序为中序遍历序列 将每一条实链转化为一棵\(BST\)
- 转化虚边:将每一条实链对应的\(BST\)的根的父亲 设置为实链的父亲(定义见上) 表示一条虚边 注意这里的虚边只从儿子指向父亲 而父亲不指向儿子
- splay维护实链
操作:
\(splay(x)\)
需要在操作之前从上到下\(pushdown\)一下翻转标记 注意必须是从上到下 \(splay\)函数有微调 相比于平衡树中的\(splay\) 少了祖父的判断
inl void push ( int x ) { if ( nroot(x) ) push(fa(x)); down(x); }
inl void splay ( int x ) { push(x); for ( int f = fa(x) ; nroot(x) ; rotate(x) , f = fa(x) ) if ( nroot(f) ) rotate ( get(x) == get(f) ? f : x ); }
\(rotate(x)\)
和朴素\(rotate\)也有微调 首先判断了普通\(rotate\)中第三条语句(\(upd\):平衡树这样写也不会出锅 那以后就这么写了)
inl void rotate ( int x )
{
int y = fa(x) , z = fa(y) , st = get(x) , sty = get(y);
nroot(y) ? connect ( x , z , sty ) : (void)(fa(x) = z);
connect ( t[x].son[!st] , y , st );
connect ( y , x , !st );
up(y) , up(x);
}
\(access(x)\)
\(access(u)\) 提取\(u\)到树根节点的路径(这里的树根是原树中的根)
目的是将\(u\)到树根节点路径上的点全部设置为实边(对应地 其他实边改为虚边 虚边改为实边 随之调整) 这样我们就得到了\(u\)节点到根节点的一条实路径 并放在一棵splay中
假设一开始我们的实边和虚边是这样划分的,那么我们的LCT就长这样(绿框中为一个splay 可能不会长这样 但是只需要中序遍历满足按照深度递增的性质即可)


我们想让最后的整个序列变为如下情况:

现在我们\(access(N)\) 将\(A-N\)的路径全部拉起来变成一条splay
那么我们首先\(splay(N)\) 因为\(N\)点已经是\(N\)所在的\(splay\)中深度最深的节点了 那么旋转上来之后只有一个右子树\(o\) 那么单方面将右子树置为\(0\)即可

接着将\(N\)指向的父亲\(I\)转到根 直接将\(N\)点接到\(I\)的右子树上即可 以此类推到根即可
inl void access ( int x ) { for ( int y = 0 ; x ; x = fa(y=x) ) splay(x) , rs(x) = y , up(x); }
\(makeroot(x)\)
将\(x\)点变成根 也就是先将\(x\)点到根节点的路径打通 再将它转上去 相当于是提着x这个点 然后其他的东西自然垂下的状态 所以这条实链中的深度需要全部翻转
inl void makeroot ( int x ) { access(x) , splay(x) , changedown(x); }
\(findroot(x)\)
判断联通性的操作 相当于是将\(x\)和这个联通块的根放到一个\(splay\)中 并返回根节点
因为\(LCT\)维护的是森林 有可能这两个点不连通
inl int findroot ( int x ) { access(x) , splay(x); while ( ls(x) ) down(x) , x = ls(x); splay(x); return x; }
\(split(x,y)\)
搞出一条\(x\)到\(y\)的实链 使得\(y\)作为根节点 也就是先将\(x\)设置为根 将\(y\)的路径打通 再将\(y\)转上去
inl void split ( int x , int y ) { makeroot(x); access(y) , splay(y); }
\(link(x,y)\)
注意 如果不联通我们才能加边 否则出现环
inl void link ( int x , int y ) { makeroot(x); if ( findroot(y) != x ) fa(x) = y; }
\(cut(x,y)\)
最麻烦的操作 所有特判都是为了保证\(x\)和\(y\)之间的路径在\(split\)操作之后中间只有一条边
inl void cut ( int x ,int y ) { split(x,y); if ( ls(y) != x || rs(x) || fa(x) != y ) return; fa(x) = ls(y) = 0 , up(x); }
下面放上模板题:
P3690 【模板】动态树(LCT)
整体代码实现:
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define fa(p) t[p].fa
#define ls(p) t[p].son[0]
#define rs(p) t[p].son[1]
const int N = 3e5 + 5;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get();
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f ;
}
int n , m , sta[N];
struct LCT
{
struct node { int fa , son[2] , rev , val , sum; } t[N];
inl void up ( int x ) { t[x].sum = t[ls(x)].sum ^ t[rs(x)].sum ^ t[x].val; }
inl void changedown ( int x ) { swap ( ls(x) , rs(x) ) , t[x].rev ^= 1; }
inl void down ( int x ) { if ( t[x].rev ) { if ( ls(x) ) changedown(ls(x)); if ( rs(x) ) changedown(rs(x)); t[x].rev ^= 1; } }
inl int nroot ( int x ) { return t[fa(x)].son[0] == x || t[fa(x)].son[1] == x; }
inl int get ( int x ) { return x == rs(fa(x)); }
inl void connect ( int x , int y , int st ) { t[y].son[st] = x , fa(x) = y; }
inl void rotate ( int x )
{
int y = fa(x) , z = fa(y) , st = get(x) , sty = get(y);
if ( nroot(y) ) connect ( x , z , sty );
fa(x) = z;
connect ( t[x].son[!st] , y , st );
connect ( y , x , !st );
up(y) , up(x);
}
inl void push ( int x ) { if ( nroot(x) ) push(fa(x)); down(x); }
inl void splay ( int x ) { push(x); for ( int f = fa(x) ; nroot(x) ; rotate(x) , f = fa(x) ) if ( nroot(f) ) rotate ( get(x) == get(f) ? f : x ); }
inl void access ( int x ) { for ( int son = 0 ; x ; x = fa(son=x) ) splay(x) , rs(x) = son , up(x); }//因为整个lct的根的父亲才是0 所以我们要转到整颗树父亲再走
inl void makeroot ( int x ) { access(x) , splay(x) , changedown(x); }
inl int findroot ( int x ) { access(x) , splay(x); while ( ls(x) ) down(x) , x = ls(x); splay(x); return x; }
inl void split ( int x , int y ) { makeroot(x); access(y) , splay(y); }
inl void link ( int x , int y ) { makeroot(x); if ( findroot(y) != x ) fa(x) = y; }
inl void cut ( int x ,int y ) { split(x,y); if ( ls(y) != x || rs(x) ) return; fa(x) = ls(y) = 0 , up(x); }
inl void change ( int x , int y ) { splay(x) , t[x].val = y; }
inl void query ( int x , int y ) { split(x,y) , cout << t[y].sum << endl; }
}L;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(nullptr) , cout.tie(nullptr);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) L.t[i].val = L.t[i].sum = read();
for ( int i = 1 , op , x , y ; i <= m ; i ++ )
{
op = read() , x = read() , y = read();
switch(op)
{
case 0 : L.query(x,y); break;
case 1 : L.link(x,y); break;
case 2 : L.cut(x,y); break;
default : L.change(x,y); break;
}
}
return 0;
}
P2147 [SDOI2008] 洞穴勘测
裸题 甚至不需要\(update\)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define fa(p) t[p].fa
#define ls(p) t[p].son[0]
#define rs(p) t[p].son[1]
const int N = 3e5 + 5;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get();
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f ;
}
int n , m;
struct LCT
{
struct node { int fa , son[2] , rev; } t[N];
inl void changedown ( int x ) { swap ( ls(x) , rs(x) ) , t[x].rev ^= 1; }
inl void down ( int x ) { if ( t[x].rev ) { if ( ls(x) ) changedown(ls(x)); if ( rs(x) ) changedown(rs(x)); t[x].rev ^= 1; } }
inl int nroot ( int x ) { return ls(fa(x)) == x || rs(fa(x)) == x; }
inl int get ( int x ) { return x == rs(fa(x)); }
inl void connect ( int x , int y , int st ) { t[y].son[st] = x , fa(x) = y; }
inl void rotate ( int x )
{
int y = fa(x) , z = fa(y) , stx = get(x) , sty = get(y);
nroot(y) ? connect ( x , z , sty ) : (void)(fa(x) = z);
connect ( t[x].son[!stx] , y , stx );
connect ( y , x , !stx );
}
void push ( int x ) { if ( nroot(x) ) push(fa(x)); down(x); }
inl void splay ( int x ) { push(x); for ( int f = fa(x) ; nroot(x) ; rotate(x) , f = fa(x) ) if ( nroot(f) ) rotate ( get(x) == get(f) ? f : x ); }
inl void access ( int x ) { for ( int son = 0 ; x ; x = fa(son=x) ) splay(x) , rs(x) = son; }
inl void makeroot ( int x ) { access(x) , splay(x) , changedown(x); }
inl int findroot ( int x ) { access(x) , splay(x); while ( ls(x) ) down(x) , x = ls(x); splay(x); return x; }
inl void split ( int x , int y ) { makeroot(x) , access(y) , splay(y); }
inl void link ( int x , int y ) { makeroot(x); if ( findroot(y) != x ) fa(x) = y; }
inl void cut ( int x , int y ) { split(x,y); if ( fa(x) == y && ls(y) == x && !rs(x) ) fa(x) = ls(y) = 0; }
inl int query ( int x , int y ) { return findroot(y) == findroot(x); }
}L;
string s;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(nullptr) , cout.tie(nullptr);
n = read() , m = read();
for ( int i = 1 , u , v ; i <= m ; i ++ )
{
cin >> s; u = read() , v = read();
if ( s[0] == 'C' ) L.link ( u , v );
if ( s[0] == 'D' ) L.cut ( u , v );
if ( s[0] == 'Q' ) cout << ( L.query ( u , v ) ? "Yes" : "No" ) << endl;
}
return 0;
}
P1501 [国家集训队] Tree II
运用了线段树\(2\)的先乘后加的思想 那么三个懒标记的操作顺序就是:乘-加-翻转(其实翻转在最前面也没有关系 但必须是先乘后加)
发现模数很小 但\(int\)存不下模数平方 所以用\(unsigned\ int\)即可
不服不行 这下超快读优化不多 但是一口氧气是真猛 干到最优解第八
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define fa(p) t[p].fa
#define ls(p) t[p].son[0]
#define rs(p) t[p].son[1]
#define int long long
const int mod = 51061;
const int N = 3e5 + 5;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f ;
}
int n , m;
struct LCT
{
struct node { int fa , son[2] , sz , val , sum , rev , add , mul; } t[N];
inl int nroot ( int x ) { return ls(fa(x)) == x || rs(fa(x)) == x; }
inl void up ( int x ) { t[x].sum = ( t[ls(x)].sum + t[rs(x)].sum + t[x].val ) % mod; t[x].sz = t[ls(x)].sz + t[rs(x)].sz + 1; }
inl void pushmul ( int x , int val ) { t[x].sum = ( t[x].sum * val ) % mod , t[x].val = ( t[x].val * val ) % mod , t[x].mul = ( t[x].mul * val ) % mod , t[x].add = ( t[x].add * val ) % mod; }
inl void pushadd ( int x , int val ) { t[x].sum = ( t[x].sum + val * t[x].sz % mod ) % mod , t[x].val = ( t[x].val + val ) % mod , t[x].add = ( t[x].add + val ) % mod; }
inl void pushrev ( int x ) { swap ( ls(x) , rs(x) ) , t[x].rev ^= 1; }
inl void down ( int x )
{
if ( t[x].rev ) { if ( ls(x) ) pushrev ( ls(x) ); if ( rs(x) ) pushrev ( rs(x) ); t[x].rev ^= 1; }
if ( t[x].mul != 1 ) pushmul ( ls(x) , t[x].mul ) , pushmul( rs(x) , t[x].mul ) , t[x].mul = 1;
if ( t[x].add ) pushadd ( ls(x) , t[x].add ) , pushadd ( rs(x) , t[x].add ) , t[x].add = 0;
}
inl int get ( int x ) { return x == rs(fa(x)); }
inl void connect ( int x , int y , int st ) { t[y].son[st] = x , fa(x) = y; }
inl void rotate ( int x )
{
int y = fa(x) , z = fa(y) , chkx = get(x) , chky = get(y);
nroot(y) ? connect ( x , z , chky ) : (void)(fa(x) = z);
connect ( t[x].son[!chkx] , y , chkx );
connect ( y , x , !chkx );
up(y) , up(x);
}
inl void push ( int x ) { if ( nroot(x) ) push(fa(x)); down(x); }
inl void splay ( int x ) { push(x); for ( int f = fa(x) ; nroot(x) ; rotate(x) , f = fa(x) ) if ( nroot(f) ) rotate ( get(f) == get(x) ? f : x ); }//splay中不要忘记下推
inl void access ( int x ) { for ( int son = 0 ; x ; x = fa(son=x) ) splay(x) , rs(x) = son , up(x); }
inl void makeroot ( int x ) { access(x) , splay(x) , pushrev(x); }
inl int findroot ( int x ) { access(x) , splay(x); while ( ls(x) ) down(x) , x = ls(x); splay(x); return x; }
inl void split ( int x , int y ) { makeroot(x) , access(y) , splay(y); }
inl void link ( int x , int y ) { makeroot(x); if ( findroot(y) != x ) fa(x) = y; }
inl void cut ( int x , int y ) { split(x,y); if ( ls(y) == x && fa(x) == y && !rs(x) ) ls(y) = fa(x) = 0; }
inl void add ( int x , int y , int val ) { split(x,y) , pushadd(y,val); }
inl void mul ( int x , int y , int val ) { split(x,y) , pushmul(y,val); }
inl int query ( int x , int y ) { split(x,y); return t[y].sum; }
}L;
char ch;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(nullptr) , cout.tie(nullptr);
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) L.t[i].sz = L.t[i].val = L.t[i].mul = 1;
for ( int i = 1 , u , v ; i < n ; i ++ ) u = read() , v = read() , L.link(u,v);
for ( int i = 1 , u , v , p , q , val ; i <= m ; i ++ )
{
ch = getchar();
u = read() , v = read();
switch(ch)
{
case '+': val = read() , L.add ( u , v , val ); break;
case '-': p = read() , q = read() , L.cut(u,v) , L.link(p,q); break;
case '*': val = read() , L.mul ( u , v , val ); break;
case '/': cout << L.query ( u , v ) << endl; break;
}
}
return 0;
}
P2173 [ZJOI2012] 网络
题中操作显然是\(lct\) 看到颜色数量很少 可以考虑为每一种颜色开一个\(lct\)维护
宏定义错了调了\(2h\) 愤怒(实际上还有\(push\)的时候\(fa(x)\)写成了\(ls(x)\))
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define fa(x) t[x].fa
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]
const int N = 1e4 + 5;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get();
int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f ;
}
int n , m , C , k , val[N];
struct LCT
{
struct node { int fa , son[2] , rev , cnt , maxx; } t[N];
inl void up ( int x ) { t[x].maxx = max ( max ( t[ls(x)].maxx , t[rs(x)].maxx ) , val[x] ); }
inl void pushrev ( int x ) { swap ( ls(x) , rs(x) ) , t[x].rev ^= 1; }
inl void down ( int x ) { if ( t[x].rev ) { if ( ls(x) ) pushrev(ls(x)); if ( rs(x) ) pushrev(rs(x)); t[x].rev ^= 1; } }
inl int get ( int x ) { return x == rs(fa(x)); }
inl int nroot ( int x ) { return ls(fa(x)) == x || rs(fa(x)) == x; }
inl void connect ( int x , int y , int st ) { t[y].son[st] = x , fa(x) = y; }
inl void rotate ( int x )
{
int y = fa(x) , z = fa(y) , chkx = get(x) , chky = get(y);
nroot(y) ? connect ( x , z , chky ) : void(fa(x) = z);
connect ( t[x].son[!chkx] , y , chkx );
connect ( y , x , !chkx );
up(y) , up(x);
}
inl void push ( int x ) { if ( nroot(x) ) push(fa(x)); down(x); }
inl void splay ( int x ) { push(x); for ( int f = fa(x) ; nroot(x) ; rotate(x) , f = fa(x) ) if ( nroot(f) ) rotate ( get(x) == get(f) ? f : x ); }
inl void access ( int x ) { for ( int son = 0 ; x ; x = fa(son=x) ) splay(x) , rs(x) = son , up(x); }
inl void makeroot ( int x ) { access(x) , splay(x) , pushrev(x); }
inl int findroot ( int x ) { access(x) , splay(x); while ( ls(x) ) down(x) , x = ls(x); splay(x); return x; }
inl void split ( int x , int y ) { makeroot(x) , access(y) , splay(y); }
inl void link ( int x , int y ) { makeroot(x) , t[x].cnt ++ , t[y].cnt ++ , fa(x) = y; }
inl void cut ( int x , int y ) { split(x,y) , t[x].cnt -- , t[y].cnt -- , fa(x) = ls(y) = 0; }
inl int querymaxx ( int x , int y ) { split(x,y); return t[y].maxx; }
}lct[11];
struct edge { int u , v; friend bool operator < ( const edge &a , const edge &b ) { return a.u == b.u ? a.v < b.v : a.u < b.u; } };
map<edge,int> mp;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(nullptr) , cout.tie(nullptr);
n = read() , m = read() , C = read() , k = read();
for ( int i = 1 ; i <= n ; i ++ ) val[i] = read();
for ( int i = 1 , u , v , w ; i <= m ; i ++ )
{
u = read() , v = read() , w = read() + 1;
lct[w].link(u,v); mp[{u,v}] = mp[{v,u}] = w;
}
for ( int i = 1 ; i <= k ; i ++ )
{
int op = read();
if ( op == 0 ) { int u = read() , v = read(); val[u] = v; for ( int j = 1 ; j <= C ; j ++ ) lct[j].splay(u); }
if ( op == 1 )
{
int u = read() , v = read() , col = read() + 1;
if ( !mp.count({u,v}) ) { cout << "No such edge." << endl; continue; }
int lstcol = mp[{u,v}];
if ( lstcol == col ) { cout << "Success." << endl; continue; }
if ( lct[col].t[u].cnt >= 2 || lct[col].t[v].cnt >= 2 ) { cout << "Error 1." << endl; continue; }
if ( lct[col].findroot(u) == lct[col].findroot(v) ) { cout << "Error 2." << endl; continue; }
cout << "Success." << endl;
lct[lstcol].cut(u,v) , lct[col].link(u,v);
mp[{u,v}] = mp[{v,u}] = col;
}
if ( op == 2 )
{
int col = read() + 1 , u = read() , v = read();
if ( lct[col].findroot(u) != lct[col].findroot(v) ) cout << -1 << endl;
else cout << lct[col].querymaxx ( u , v ) << endl;
}
}
return 0;
}