LCT 进阶指南

这里总结了LCT的进阶应用, 以及几道比较难的例题

强烈推荐 : https://www.cnblogs.com/flashhu/p/9498517.html

目录

LCT 维护双强连通分量

LCT 维护子树信息

例题 : 

P2542 [AHOI2005]航线规划

U19464 山村游历(Wander)

P4299 首都

BZOJ4998星球联盟

BZOJ2959长跑

P3613 睡觉困难综合征


LCT 维护双强连通分量

可以说是LCT 套一个并查集, 但是只支持插入不能删除

我们没插入一条边(x, y), 有以下 3 种情况

1.本身在一个强联通分量, 直接跳过

2.不在一个强联通分量, 但连通, 这时我们将 x makeroot, y access , y splay, 然后将路径上的点全部用并查集合并到 x 上

3. 不连通, 相当于Link

注意一旦要用某个点x, 我们不能直接用, 要用 x 的在并查集中的祖先

也就是说一个强联通分量里的所有操作都用祖先那个点的编号代替了

LCT 维护子树信息

核心操作就是统一维护虚子树的信息, Access更新, 维护子树的代码如下

// siz 为虚子树, t[x].siz 维护整个子树的siz
void Pushup(int x){
	t[x].siz = t[ls].siz + t[rs].siz + siz[x] + 1;
}
void Access(int x){
	for(int y = 0; x; y = x, x = t[x].fa){
		Splay(x);
		siz[x] += t[rs].siz;
		rs = y;
		siz[x] -= t[rs].siz;
		Pushup(x);
	}
}

另外, link 操作将x置为y的序儿子, 要将x的siz 加到y的虚子树siz上

bool Link(int x, int y){
	Makeroot(x); 
	t[x].fa = y; siz[y] += t[x].siz;
	Pushup(y); 
}

P2542 [AHOI2005]航线规划

我们将所有双强联通分量缩成一个点过后, x 到 y 路径点的个数 - 1 就是答案

#include
#define N 100050
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;} 
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int n, m, T;
struct Qu{int op, x, y;}q[N];
#define ls t[x].ch[0]
#define rs t[x].ch[1]
struct Node{
	int ch[2], fa, siz, rev;
}t[N];
struct Edge{ int x, y;} E[N];
map , int> vis;
int fa[N];
int find(int x){ return x == fa[x] ? x : fa[x] = find(fa[x]);}
int ans[N], res;

bool isRoot(int x){
	int fa = t[x].fa;
	return t[fa].ch[0] != x && t[fa].ch[1] != x;
}
void Pushup(int x){ t[x].siz = t[ls].siz + t[rs].siz + 1;}
void Pushrev(int x){
	if(!x) return; swap(ls, rs); t[x].rev ^= 1;
}
void Pushdown(int x){
	if(t[x].rev){ Pushrev(ls); Pushrev(rs); t[x].rev = 0;}
} 
void Pushpath(int x){
	if(!isRoot(x)) Pushpath(t[x].fa);
	Pushdown(x);
}
void rotate(int x){
	int y = t[x].fa, z = t[y].fa;
	int k = t[y].ch[1] == x;
	if(!isRoot(y)) t[z].ch[t[z].ch[1] == y] = x;
	t[x].fa = z;
	t[y].ch[k] = t[x].ch[k^1];
	t[t[x].ch[k^1]].fa = y;
	t[x].ch[k^1] = y; t[y].fa = x;
	Pushup(y); Pushup(x);
}
void Splay(int x){
	Pushpath(x);
	while(!isRoot(x)){
		int y = t[x].fa, z = t[y].fa;
		if(!isRoot(y))
			(t[y].ch[0] == x) ^ (t[z].ch[0] == y) ? rotate(x) : rotate(y);
		rotate(x);
	} 
}
void Access(int x){
	for(int y = 0; x; y = x, x = t[x].fa = find(t[x].fa))
		Splay(x), rs = y, Pushup(x);
}
void Makeroot(int x){ 
	Access(x); Splay(x); Pushrev(x);
}
int Findroot(int x){
	Access(x); Splay(x);
	Pushdown(x); while(ls) x = ls, Pushdown(x);
	Splay(x); return x;
}
int Quary(int x, int y){
	Makeroot(x); Access(y); Splay(y);
	return t[y].siz - 1;
}
void Del(int x,int goal){
	if(x) fa[x] = goal, Del(ls, goal), Del(rs, goal);
}
void Merge(int x, int y){
	if(x == y) return;
	Makeroot(x);
	if(Findroot(y) != x){ t[x].fa = y; return;}
	Del(rs, x); rs = 0; Pushup(x);
}
int main(){
	n = read(), m = read();
	for(int i=1; i<=n; i++) fa[i] = i;
	for(int i=1; i<=m; i++){
		E[i].x = read(), E[i].y = read();
		if(E[i].x > E[i].y) swap(E[i].x, E[i].y);
	}
	for(T=1; ; T++){
		int op = read(); if(op == -1) break;
		int x = read(), y = read(); if(x > y) swap(x, y);
		if(op == 0) vis[make_pair(x,y)] = 1;
		q[T] = (Qu){op, x, y};
	}
	for(int i=1; i<=m; i++){
		if(vis[make_pair(E[i].x, E[i].y)]) continue;
		Merge(find(E[i].x), find(E[i].y));
	}
	for(int i=T; i>=1; i--){
		int op = q[i].op, x = q[i].x, y = q[i].y;
		x = find(x), y = find(y);
		if(op == 0) Merge(x, y);
		if(op == 1) ans[++res] = Quary(x, y);
	} 
	for(int i=res; i>=1; i--) printf("%d\n",ans[i]);
	return 0;
}

U19464 山村游历(Wander)

对于 x -- y 路径上的每一个点, 如果它的儿子能走到y, 我们称之为走对了, 否则为走偏了

显然如果走偏了就要把那个子树走完, 贡献为siz * 2

于是每一个子树的贡献都是  siz * 2 * P(在走对之前走偏)

注意这是随机的排列。对于每个排列,有且仅有另一个排列与其顺序相反。

如果有一个排列,某一子树排在了路径边的前面(需要计算size)

那么必定有且仅有另一个对应的排列使得该子树排在路径边的后面(不需要计算size)。

由于这种等概率的对应关系,p=1/2

于是就维护子树siz, split(x,y) 然后 siz(x) - siz(y) - 1 (y的大小) 就是答案

#include
#define N 100050
#define ls t[x].ch[0]
#define rs t[x].ch[1]
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;} 
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
struct Node{int ch[2], fa, rev, siz;}t[N];
int siz[N], n, m;
bool isRoot(int x){
	int fa = t[x].fa;
	return t[fa].ch[0] != x && t[fa].ch[1] != x; 
}
void Pushup(int x){
	t[x].siz = t[ls].siz + t[rs].siz + siz[x] + 1;
}
void Pushrev(int x){
	if(!x) return; swap(ls, rs); t[x].rev ^= 1;
} 
void Pushdown(int x){
	if(t[x].rev) Pushrev(ls), Pushrev(rs), t[x].rev = 0;
}
void rotate(int x){
	int y = t[x].fa, z = t[y].fa;
	int k = t[y].ch[1] == x;
	if(!isRoot(y)) t[z].ch[t[z].ch[1] == y] = x;
	t[x].fa = z;
	t[y].ch[k] = t[x].ch[k^1];
	t[t[x].ch[k^1]].fa = y;
	t[x].ch[k^1] = y; t[y].fa = x;
	Pushup(y); Pushup(x);
}
void Pushpath(int x){
	if(!isRoot(x)) Pushpath(t[x].fa);
	Pushdown(x); 
}
void Splay(int x){
	Pushpath(x);
	while(!isRoot(x)){
		int y = t[x].fa, z = t[y].fa;
		if(!isRoot(y))
			(t[y].ch[0] == x) ^ (t[z].ch[0] == y) ? rotate(x) : rotate(y);
		rotate(x); 
	}
}
void Access(int x){
	for(int y = 0; x; y = x, x = t[x].fa){
		Splay(x);
		siz[x] += t[rs].siz;
		rs = y;
		siz[x] -= t[rs].siz;
		Pushup(x);
	}
}
int Findroot(int x){
	Access(x); Splay(x); Pushdown(x);
	while(ls) x = ls, Pushdown(x);
	return x;
}
void Makeroot(int x){
	Access(x); Splay(x); Pushrev(x);
}
bool Link(int x, int y){
	Makeroot(x); 
	if(Findroot(y) == x) return false;
	t[x].fa = y; siz[y] += t[x].siz;
	Pushup(y); return true;
}
bool Cut(int x, int y){
	Makeroot(x);
	if(Findroot(y) != x) return false;
	Access(y); Splay(y); 
	if(t[x].ch[1] || t[x].fa != y) return false;
	t[y].ch[0] = t[x].fa = 0; Pushup(y);
	return true;
}
int main(){
	n = read(), m = read();
	while(m--){
		int op = read(), u = read(), v = read();
		if(op == 0){
			if(Link(u, v)) printf("OK\n"); 
			else printf("ILLEGAL\n");
		}
		if(op == 1){
			if(Cut(u, v)) printf("OK\n");
			else printf("ILLEGAL\n");
		}
		if(op == 2){ 
			Makeroot(u);
			if(Findroot(v) != u) printf("ILLEGAL\n");
			else{
				Access(v); Splay(v);
				printf("%d.0000\n", t[v].siz - siz[v] - 1);
			}
		}
	} return 0;
} 

P4299 首都

相当于LCT动态维护重心

我们考虑两个分别求出重心的联通快, 发现新的重心一定在两个重心的连线上

我们将这两个点Split, 然后在Splay上二分查找新的重心, 然后用并查集维护每个点的重心

#include
#define N 100050
#define ls t[x].ch[0]
#define rs t[x].ch[1]
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;} 
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
struct Node{int ch[2], fa, siz, rev;} t[N];
int fa[N], siz[N];
int find(int x){ return x == fa[x] ? x : fa[x] = find(fa[x]);}
int n, m, Xor;
bool isRoot(int x){
	int fa = t[x].fa;
	return t[fa].ch[0] != x && t[fa].ch[1] != x; 
}
void Pushup(int x){
	t[x].siz = t[ls].siz + t[rs].siz + siz[x] + 1;
}
void Pushrev(int x){
	if(!x) return; swap(ls, rs); t[x].rev ^= 1;
} 
void Pushdown(int x){
	if(t[x].rev) Pushrev(ls), Pushrev(rs), t[x].rev = 0;
}
void rotate(int x){
	int y = t[x].fa, z = t[y].fa;
	int k = t[y].ch[1] == x;
	if(!isRoot(y)) t[z].ch[ t[z].ch[1] == y ] = x;
	t[x].fa = z;
	t[y].ch[k] = t[x].ch[k^1];
	t[t[x].ch[k^1]].fa = y;
	t[x].ch[k^1] = y; t[y].fa = x;
	Pushup(y); Pushup(x);
}
void Pushpath(int x){
	if(!isRoot(x)) Pushpath(t[x].fa);
	Pushdown(x); 
}
void Splay(int x){
	Pushpath(x);
	while(!isRoot(x)){
		int y = t[x].fa, z = t[y].fa;
		if(!isRoot(y))
			(t[y].ch[0] == x) ^ (t[z].ch[0] == y) ? rotate(x) : rotate(y);
		rotate(x);
	}
}
void Access(int x){
	for(int y = 0; x; y = x, x = t[x].fa){
		Splay(x);
		siz[x] += t[rs].siz;
		rs = y;
		siz[x] -= t[rs].siz;
		Pushup(x);
	}
} 
void Makeroot(int x){
	Access(x); Splay(x); Pushrev(x);
}
void Link(int x, int y){
	Makeroot(x); Makeroot(y);
	t[x].fa = y; siz[y] += t[x].siz; Pushup(y);
	x = find(x); y = find(y);
	Makeroot(x); Access(y); Splay(y);
	int pos = n+1, now = y, lsiz = 0, rsiz = 0, goal = t[y].siz>>1;
	while(now){
		Pushdown(now);
		int lc = t[now].ch[0], rc = t[now].ch[1];
		int tmpl = lsiz + t[lc].siz, tmpr = rsiz + t[rc].siz;
		if(tmpl <= goal && tmpr <= goal) pos = min(pos, now); 
		if(tmpl > tmpr) rsiz += t[rc].siz + siz[now] + 1, now = lc;
		else lsiz += t[lc].siz + siz[now] + 1, now = rc;
	} Splay(pos);
	fa[pos] = fa[x] = fa[y] = pos; Xor ^= x ^ y ^ pos;
}
int main(){
	n = read(), m = read();
	for(int i=1; i<=n; i++) fa[i] = i, Xor ^= i;
	while(m--){
		char s[5]; scanf("%s", s);
		if(s[0] == 'X') printf("%d\n", Xor); 
		if(s[0] == 'A'){
			int x = read(), y = read();
			Link(x, y);
		}
		if(s[0] == 'Q'){
			int x = read(); 
			printf("%d\n", find(x));
		}
	} return 0; 
} 

BZOJ4998星球联盟

LCT维护双强联通分量, 然后并查集在维护一个siz就可以了

#include
#define N 200050
#define ls t[x].ch[0]
#define rs t[x].ch[1]
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;} 
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
struct Node{int ch[2], fa, rev;}t[N];
int fa[N], siz[N];
int find(int x){ return x == fa[x] ? x : fa[x] = find(fa[x]);}
int n, m, p;
bool isRoot(int x){
	int fa = t[x].fa; 
	return t[fa].ch[0] != x && t[fa].ch[1] != x;
}
void Pushrev(int x){
	if(!x) return; swap(ls, rs); t[x].rev ^= 1;
}
void Pushdown(int x){
	if(t[x].rev){ Pushrev(ls); Pushrev(rs); t[x].rev = 0;}
}
void Pushpath(int x){
	if(!isRoot(x)) Pushpath(t[x].fa);
	Pushdown(x);
}
void rotate(int x){
	int y = t[x].fa, z = t[y].fa;
	int k = t[y].ch[1] == x;
	if(!isRoot(y)) t[z].ch[t[z].ch[1] == y] = x;
	t[x].fa = z;
	t[y].ch[k] = t[x].ch[k^1]; 
	t[t[x].ch[k^1]].fa = y;
	t[x].ch[k^1] = y; t[y].fa = x; 
}
void Splay(int x){
	Pushpath(x);
	while(!isRoot(x)){
		int y = t[x].fa, z = t[y].fa;
		if(!isRoot(y)) 
			(t[y].ch[0] == x) ^ (t[z].ch[0] == y) ? rotate(x) : rotate(y);
		rotate(x);
	}
}
void Access(int x){
	for(int y = 0; x; y = x, x = t[x].fa = find(t[x].fa))
		Splay(x), rs = y;
}
void Makeroot(int x){
	Access(x); Splay(x); Pushrev(x);
}
int Findroot(int x){
	Access(x); Splay(x);
	Pushdown(x); while(ls) x = ls, Pushdown(x);
	Splay(x); return x;
}
void Del(int x, int goal){
	if(x) fa[x] = goal, siz[goal] += siz[x], Del(ls, goal), Del(rs, goal);
}
void Merge(int x, int y){
	if(x == y) return;
	Makeroot(x);
	if(Findroot(y) != x){ t[x].fa = y; return;}
	Del(rs, x); rs = 0;
} 
int main(){
	n = read(), m = read(), p = read();
	for(int i=1; i<=n; i++) fa[i] = i, siz[i] = 1;
	for(int i=1; i<=m; i++){
		int x = read(), y = read();
		Merge(find(x), find(y));
	}
	for(int i=1; i<=p; i++){
		int x = read(), y = read();
		Merge(find(x), find(y));
		x = find(x), y = find(y);
		if(x == y) printf("%d\n", siz[x]);
		else printf("No\n");
	} return 0;
}

BZOJ2959长跑

刷卡的最多次数就是将路径上所有的环走完的点数

我们将这些环缩成一个点, 查询时Split(x,y)就可以了

#include
#define N 200050
#define ls t[x].ch[0]
#define rs t[x].ch[1]
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;} 
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
struct Node{int ch[2], fa, rev, val;}t[N];
int fa[N], a[N], val[N];
int find(int x){ return x == fa[x] ? x : fa[x] = find(fa[x]);}
int n, m;
bool isRoot(int x){
	int fa = t[x].fa; 
	return t[fa].ch[0] != x && t[fa].ch[1] != x;
}
void Pushup(int x){ t[x].val = val[x] + t[ls].val + t[rs].val;}
void Pushrev(int x){
	if(!x) return; swap(ls, rs); t[x].rev ^= 1;
}
void Pushdown(int x){
	if(t[x].rev){ Pushrev(ls); Pushrev(rs); t[x].rev = 0;}
}
void Pushpath(int x){
	if(!isRoot(x)) Pushpath(t[x].fa);
	Pushdown(x);
}
void rotate(int x){
	int y = t[x].fa, z = t[y].fa;
	int k = t[y].ch[1] == x;
	if(!isRoot(y)) t[z].ch[t[z].ch[1] == y] = x;
	t[x].fa = z;
	t[y].ch[k] = t[x].ch[k^1]; 
	t[t[x].ch[k^1]].fa = y;
	t[x].ch[k^1] = y; t[y].fa = x; 
	Pushup(y); Pushup(x);
}
void Splay(int x){
	Pushpath(x);
	while(!isRoot(x)){
		int y = t[x].fa, z = t[y].fa;
		if(!isRoot(y)) 
			(t[y].ch[0] == x) ^ (t[z].ch[0] == y) ? rotate(x) : rotate(y);
		rotate(x);
	}
}
void Access(int x){
	for(int y = 0; x; y = x, x = t[x].fa = find(t[x].fa))
		Splay(x), rs = y, Pushup(x);
}
void Makeroot(int x){
	Access(x); Splay(x); Pushrev(x);
}
int Findroot(int x){
	Access(x); Splay(x);
	Pushdown(x); while(ls) x = ls, Pushdown(x);
	Splay(x); return x;
}
void Del(int x, int goal){
	if(x) fa[x] = goal, val[goal] += val[x], Del(ls, goal), Del(rs, goal);
}
void Merge(int x, int y){
	if(x == y) return;
	Makeroot(x);
	if(Findroot(y) != x){ t[x].fa = y; return;}
	Del(rs, x); rs = 0;
} 
int main(){
	n = read(), m = read();
	for(int i=1; i<=n; i++) scanf("%d", &val[i]), a[i] = val[i];
	for(int i=1; i<=n; i++) fa[i] = i;
	while(m--){
		int op = read(), x = read(), y = read();
		if(op == 1) Merge(find(x), find(y));
		if(op == 2){ 
			val[find(x)] -= a[x]; 
			a[x] = y;
			val[find(x)] += a[x];
			x = find(x);
			Access(x); Splay(x); Pushup(x);
		}
		if(op == 3){ 
			x = find(x), y = find(y);
			Makeroot(x); 
			if(Findroot(y) != x) printf("-1\n"); 
			else{
				Access(y); Splay(y); 
				printf("%d\n", t[y].val);
			}
		}
	} return 0;
}

P3613 睡觉困难综合征

按位处理是显然的, 我们用树剖或者LCT维护都可以, 主要是区间如何合并

我们用 L.f0[i] 表示 对于第i位, 左区间进去是0, 出来是什么, R表示右区间, X表示合并后的区间

X.f0[i] = (L.f0[i] & R.f1[i]) | ( ! L.f0[i] & R.f0[i] )

但如果一位一位考虑是要超时的, 我们能不能压到一个long long 里来一起位运算呢

于是有  X.f0 = (L.f0 & R.f1) | (~L.f0 & R.f0) , f1 同理

我用的LCT维护, 因为这样可以忽略树剖转向的问题, 但LCT 区间翻转时记得把左右信息交换

最后查询答案时, 我们按位贪心, 如果当前位0走进1走出, 就可以直接选上

如果1走进1走出, 且当前位的值 <= z的限制, 也可以选上

#include
#define N 200050
#define LL unsigned long long
#define ls t[x].ch[0]
#define rs t[x].ch[1]
using namespace std;
LL read(){
	LL cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;} 
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
struct Node{
	LL f0, f1; 
	friend Node operator + (const Node &a, const Node &b){
		Node c;
		c.f0 = (~a.f0 & b.f0) | (a.f0 & b.f1);
		c.f1 = (a.f1 & b.f1) | (~a.f1 & b.f0);
		return c;
	}
}a[N]; 
struct Tree{
	int ch[2], fa, rev; Node lv, rv;
}t[N];
int n, m, k;
bool isRoot(int x){
	int fa = t[x].fa;
	return t[fa].ch[0] != x && t[fa].ch[1] != x; 
}
void Pushup(int x){
	t[x].lv = t[x].rv = a[x];
	if(ls) t[x].lv = t[ls].lv + t[x].lv, t[x].rv = t[x].rv + t[ls].rv;
	if(rs) t[x].lv = t[x].lv + t[rs].lv, t[x].rv = t[rs].rv + t[x].rv;
}
void Pushrev(int x){
	if(!x) return; swap(ls, rs); swap(t[x].lv, t[x].rv); t[x].rev ^= 1;
} 
void Pushdown(int x){
	if(t[x].rev) Pushrev(ls), Pushrev(rs), t[x].rev = 0;
}
void rotate(int x){
	int y = t[x].fa, z = t[y].fa;
	int k = t[y].ch[1] == x;
	if(!isRoot(y)) t[z].ch[t[z].ch[1] == y] = x;
	t[x].fa = z;
	t[y].ch[k] = t[x].ch[k^1];
	t[t[x].ch[k^1]].fa = y;
	t[x].ch[k^1] = y; t[y].fa = x;
	Pushup(y); Pushup(x);
}
void Pushpath(int x){
	if(!isRoot(x)) Pushpath(t[x].fa);
	Pushdown(x); 
}
void Splay(int x){
	Pushpath(x);
	while(!isRoot(x)){
		int y = t[x].fa, z = t[y].fa;
		if(!isRoot(y))
			(t[y].ch[0] == x) ^ (t[z].ch[0] == y) ? rotate(x) : rotate(y);
		rotate(x); 
	}
}
void Access(int x){
	for(int y = 0; x; y = x, x = t[x].fa)
		Splay(x), rs = y, Pushup(x);
}
void Makeroot(int x){
	Access(x); Splay(x); Pushrev(x);
}
void Link(int x, int y){
	Makeroot(x); t[x].fa = y;
}
int main(){
	n = read(), m = read(), k = read();
	for(int i=1; i<=n; i++){
		int op = read(); LL val = read();
		if(op == 1){ a[i] = (Node){0, val};}
		if(op == 2){ a[i] = (Node){val, ~0};}
		if(op == 3){ a[i] = (Node){val, ~val};}
	}
	for(int i=1; i=0; i--){
				if(t[y].lv.f0 & (e<= (e<

 

你可能感兴趣的:(LCT)