LCT

LCT

前置芝士:

  1. 链剖分:指一类对树的边进行轻重划分的操作 这样做的目的是为了减少某些链上的修改/查询操作的复杂度

    目前有:重链剖分 实链剖分 和并不常见的长链剖分

  2. 实链剖分:将某一个儿子的连边划分为实边 其他边划分为虚边

    区别就在于虚实是动态变化的 因此需要使用splay来维护每一条链

    一些定义:

    • 实边/虚边:和树剖中的轻重边相似 但注意 对于某一些非叶子节点 可能连边都是虚边
    • 实路径:用若干条实边首尾相连构成的路径
    • 实链:由实边构成的极大路径
    • 实链的父亲:实链中深度最小的点的父亲 也就是原来这条链在原树中的父亲

LCT与树剖本质区别就是:LCT维护的是动态的树 树剖只能维护静态 最多支持修改

应用场景:

  1. 给出一棵树(或森林),树的形态可变化。
  2. 查询、修改树上路径的信息(最值,总和等)
  3. 更换树根
  4. 动态连边(通过连边合并两棵树)、删边(通过删边将一棵树一分为二)
  5. 询问某两个节点是否连通

基本结构:

将原树转化为辅助树

  1. 剖分:将原树剖分成实链和虚边 原树可能是无根树 但我们通常看成有根树来处理
  2. 转化实链: 以深度从小到大的次序为中序遍历序列 将每一条实链转化为一棵\(BST\)
  3. 转化虚边:将每一条实链对应的\(BST\)的根的父亲 设置为实链的父亲(定义见上) 表示一条虚边 注意这里的虚边只从儿子指向父亲 而父亲不指向儿子
  4. 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 可能不会长这样 但是只需要中序遍历满足按照深度递增的性质即可)

img img

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

img

现在我们\(access(N)\)\(A-N\)的路径全部拉起来变成一条splay

那么我们首先\(splay(N)\) 因为\(N\)点已经是\(N\)所在的\(splay\)中深度最深的节点了 那么旋转上来之后只有一个右子树\(o\) 那么单方面将右子树置为\(0\)即可

img

接着将\(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;
}
posted @ 2023-07-30 17:51  Echo_Long  阅读(6)  评论(0编辑  收藏  举报