Link/cut tree

LCT的讲解 wikipedia 说的挺清楚的LCT——wikipedia


LCT的重点好像还是不到十行,就是一个access操作.

access实际上干的事情呢, 就是把这个点到根的这条路打通(都成为实(重)边)。并且用一棵二叉排序树来维护这条路(一个点属于这棵二叉排序树当且仅当它是那个点的祖先)。另外, 这棵树排序的标准是一个点的左儿子的深度小于自己。


http://upload.wikimedia.org/wikipedia/commons/9/98/Linkcuttree1.png

这幅图就是access操作了。

实现的时候呢, 就从x 到root把这条链建出来就好了。如果我们已经建好了从x到 i 的这条链, 也就是说下一步加上 i 到 i 的父亲这一条链,这时候我们只要把 i  splay到它所在的这棵树的最顶端, 然后断开它和右儿子的链接(因为它下面那条链是要连着i的, 不能连其它的点), 然后 再把 i 作为 这个右儿子就可以了, 还是很显然的。

写代码时也不用考虑断开接上什么的, 因为 fat 这个数组, 可以有两种含义, 当它是当前树的根的时候, fat连的是原图中的父节点(也就是上一棵树中深度比当前树种深度最小的点小一的那个点), 而当它不是当前树的根的时候, fat连的就是 它在二叉排序树种的父亲(如果这个节点是左儿子, 那么连的就是它在原图中的儿子, 否则连的是它在原图中的父亲)。 所以在进行所谓的断开或者接上操作后, fat这个数组是没有发生变化的, 唯一变化的就是当前这个节点还是不是当前子树的根 (当然这个变化也可以没有因为如果不开一个bool的数组标记root的时候也可以直接看一看它的fat的两个child是不是都不等于自己, 如果都不等于的话它就一定是root了), 所以开一下child数组和root数组就好了。

void access(int x){
	int y = 0;
	do{
		splay(x);
		rt[ch[x][1]] = 1;
		rt[ch[x][1] = y] = 0;
		pushup(x);
		x = fat[y = x];
	}while(x);
}


UPD :

现在更喜欢这样写access:这样写就可以少一个 求 LCA 的函数啦!

inline int access(int x){
    int y;
    for(y = 0; x; x = fat[y = x]){
        splay(x);
        rt[ch[x][1]] = 1;
        rt[ch[x][1] = y] = 0;
        pushup(x);    
    }    return y;
}

简单题:


bzoj 2002 弹飞绵羊(这个名字真的好可爱啊我受不了了。。越想越可爱n(*≧▽≦*)n)

这道题呢, 只需要维护原树中到root的距离, 所以把它access一下再splay一下看一下左儿子有多大就可以解决了。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 200005
using namespace std;
int n, fat[MAXN], ch[MAXN][2], sz[MAXN];
bool root[MAXN];
inline void pushup(int x){sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1;}
inline void jie(int x, int y, int k){ch[y][k] = x; fat[x] = y;}
void rotate(int x, int k){
	int y = fat[x];jie(ch[x][k], y, !k);
	fat[x] = fat[y]; jie(y, x, k);
	if(root[y])root[y] = 0, root[x] = 1;
	else ch[fat[x]][ch[fat[x]][1] == y] = x;
	pushup(y);
}
void splay(int x){
	while(!root[x]){
		int y = fat[x], ky = ch[fat[y]][1] == y;
		if(root[y]){rotate(x, ch[y][0] == x); break;}
		if(ch[y][ky] == x)rotate(y, !ky), rotate(x, !ky);
		else rotate(x, ky), rotate(x, !ky);
	}pushup(x);
}
void access(int x){
	int y = 0;
	do{
		splay(x);
		root[ch[x][1]] = 1;
		root[ch[x][1] = y] = 0;
		pushup(x);
		x = fat[y = x];
	}while(x);
}
int main(){
    scanf("%d", &n);int xx, aa, bb, kk;
    memset(root, true, sizeof(root));
    for(int i = 1; i <= n + 1; i ++)sz[i] = 1;
    for(int i = 1; i <= n; i ++){
        scanf("%d", &xx); fat[i] = (i + xx > n) ? n + 1: xx + i;
    }int q; scanf("%d", &q); while(q --){
        scanf("%d%d", &kk, &aa); aa ++;
        if(kk == 1){
            access(aa); splay(aa);
            printf("%d\n", sz[ch[aa][0]]);
        }else{ scanf("%d", &bb);
            access(aa); splay(aa);
            fat[ch[aa][0]] = 0;
            root[ch[aa][0]] = 1;
            ch[aa][0] = 0;
            sz[aa] = sz[ch[aa][1]] + 1;
            fat[aa] = (aa + bb > n) ? n + 1: bb + aa;
        }
    } //system("pause");
    return 0;
}

spoj 375 QTREE

一道裸的树剖题, 但是多了一个 询问lca的操作。

其实询问lca的操作也很简单。 考虑把一个点进行了 access 操作, 这时候如果 要求它和另一个点的lca 的话, 它们的lca 存在 且只存在 从 这个点到根的这条链上, 即这个点存在的(也是root存在的)那棵splay上, 所以我们只需要把另一个点一层一层地叠上去看一下 它是否在这棵树上就可以了。当然在叠上去的时候也是要靠splay 来做的, 每次把它splay一下就可以了, 直到splay了之后它的父亲是 0, 也就是它在 那条链上了。

综上, lca的代码写起来和 access 的基本上一样。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 10005
using namespace std;
int n, ee, head[MAXN], fat[MAXN], ch[MAXN][2], key[MAXN], ti[MAXN], maxx[MAXN];
bool rt[MAXN];
struct Edge{
	int to, next, c, idd;
}edge[MAXN * 2];
inline void addedge(int s, int y, int c, int dd){
	edge[++ ee].to = y;
	edge[ee].next = head[s];
	edge[ee].idd = dd;
	edge[ee].c = c;
	head[s] = ee;
}
void dfs(int x){
	for(int i = head[x]; i != -1; i = edge[i].next)if(fat[edge[i].to] == 0){
		key[edge[i].to] = edge[i].c, 
		fat[edge[i].to] = x, 
		ti[edge[i].idd] = edge[i].to, 
		dfs(edge[i].to);
	}
}
inline void pushup(int x){maxx[x] = max(max(maxx[ch[x][0]], maxx[ch[x][1]]), key[x]);}
inline void jie(int x, int y, int k){fat[x] = y; ch[y][k] = x;}
inline void rotate(int x, int k){
	int y = fat[x];
	jie(ch[x][k], y, !k);
	fat[x] = fat[y];
	jie(y, x, k);
	if(rt[y])rt[y] = 0, rt[x] = 1;
	else ch[fat[x]][ch[fat[x]][1] == y] = x;
	pushup(y);
}
void splay(int x){
	while(!rt[x]){
		int y = fat[x], ky = ch[fat[y]][1] == y;
		if(rt[y]){rotate(x, ch[y][0] == x); break;}
		if(ch[y][ky] == x)rotate(y, !ky), rotate(x, !ky);
		else rotate(x, ky), rotate(x, !ky);
	}	pushup(x);
}
void access(int x){
	int y = 0;
	do{
		splay(x);
		rt[ch[x][1]] = 1;
		rt[ch[x][1] = y] = 0;
		pushup(x);
		x = fat[y = x];
	}while(x);
}
void change(int x, int c){//cout<<x<<endl;
	access(x);
	key[x] = c;
	pushup(x);
}
void query(int u, int v){
	access(v), v = 0;
	while(u){
		splay(u);//cout<<u<<endl;
		if(!fat[u]){printf("%d\n", max(maxx[v], maxx[ch[u][1]])); return;}
		rt[ch[u][1]] = 1;
		rt[ch[u][1] = v] = 0;
		pushup(u);
		u = fat[v = u];
	}
}
int main(){
	int t; scanf("%d", &t); while(t --){
		scanf("%d", &n); ee = 0;
		memset(rt, 1, sizeof(rt));
		memset(ch, 0, sizeof(ch));
		memset(fat, 0, sizeof(fat));
		memset(head, -1, sizeof(head));
		maxx[0] = -(1<<30);
		for(int i = 1; i < n; i ++){
			int aa, bb, cc; scanf("%d%d%d", &aa, &bb, &cc);
			addedge(aa, bb, cc, i), addedge(bb, aa, cc, i);
		} fat[1] = -1; dfs(1); fat[1] = 0;
		char s[20], aa, bb; 
		while(scanf("%s", s)){if(s[0] == 'D')break;
			int aa, bb; scanf("%d%d", &aa, &bb);
			if(s[0] == 'C')change(ti[aa], bb);
			else query(aa, bb);
		}
	}
	return 0;
}


bzoj 2049 洞穴勘测

第一道真正的link / cut tree

前面那两道都只能算是splay 启发式合并吧,并没有真正地进行 link 和cut 的操作。

这道题就是一个带有link cut的裸题。

link 的时候, 最直接的想法就是把一个 点的 fat 设为另一个点, 但是每个点 都已经有fat了, 没法设。 唯一没有fat的点就是 整棵辅助树的root节点(并不是一棵splay的root节点, 那个节点也有一个连着上一棵splay的虚边), 所以说这要把一个点设为 总体的root, 然后把它的fat设为另一个点就可以了。

cut的时候, 直接的想法是要判断一下两个节点哪个更靠近当前这棵splay的根, 然后把它们之间的那条边断掉。 但是看在判断哪个靠近根还要再写一个函数, 我们可以先把X设为根, 然后在access y 并splay y, 使得这时候y在根, 然后这时很显然x , 就是y 的左儿子。 所以断掉y的左儿子就可以了。

 

代码只有70行, 而且还有60行是上一题也写了的, 但是我狂RE 了三天, 每天早上敲一遍这题也真是醉了, 最后发现我就是在cut 完了以后忘了把cut下来的那个点的root设为1了!!! ~~~~(>_<)~~~~ 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define MAXN 10005
using namespace std;
int n, m, fat[MAXN], ch[MAXN][2];
bool rev[MAXN], root[MAXN];
void reverse(int x){if(!x)return;
	rev[x] ^= 1; swap(ch[x][0], ch[x][1]);
}
inline void pushdown(int x){if(!rev[x])return;
	rev[x] = 0; reverse(ch[x][0]); reverse(ch[x][1]);
}
inline void jie(int x, int y, int k){ch[y][k] = x; fat[x] = y;}
inline void rotate(int x, int k){
	int y = fat[x]; 
	jie(ch[x][k], y, !k);
	fat[x] = fat[y];
	jie(y, x, k);
	if(root[y])root[y] = 0, root[x] = 1;
	else ch[fat[x]][ch[fat[x]][1] == y] = x;
}
void Pushdown(int x){
	if(!root[x])Pushdown(fat[x]);
	pushdown(x);
}
inline void splay(int x){    
	Pushdown(x);
	while(!root[x]){
		int y = fat[x], ky = ch[fat[y]][1] == y;
		if(root[y]){rotate(x, ch[y][0] == x);break;}
		if(ch[y][ky] == x)rotate(y, !ky), rotate(x, !ky);
		else rotate(x, ky), rotate(x, !ky);
	}
}
void access(int x){
	int y = 0;
	do{
		splay(x);
		root[ch[x][1]] = 1;
		root[ch[x][1] = y] = 0;
		x = fat[y = x];
	}while(x);
}
bool ask(int x, int y){
	while(fat[x])x = fat[x];
	while(fat[y])y = fat[y];
	return x == y;
}
bool makeroot(int x){
	access(x), splay(x), reverse(x);
}
void link(int x, int y){
	makeroot(x); fat[x] = y;
}
void cut(int x, int y){
	makeroot(x); access(y); splay(y); ch[y][0] = fat[x] = 0; root[x] = 1;
}
int main(){
	scanf("%d%d", &n, &m);
	memset(root, 1, sizeof(root));
	while(m --){ char s[20]; int x, y;
		scanf("%s%d%d", s, &x, &y);
		if(s[0] == 'C')link(x, y);
		if(s[0] == 'D')cut(x, y);
		if(s[0] == 'Q')ask(x, y) ? puts("Yes") : puts("No");
	}
	return 0;
}


bzoj 3282

最简单的LCT, 可喜可贺的是我没有调试, 让人一下子产生了LCT真的好简单啊的想cuo法jue

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdio>
#define MAXN 300005
using namespace std;
int n, m, val[MAXN], fat[MAXN], ch[MAXN][2], s[MAXN];
bool rev[MAXN], root[MAXN];
inline void pushup(int x){s[x] = val[x] ^ s[ch[x][0]] ^ s[ch[x][1]];}
inline void reverse(int x){
    rev[x] ^= 1; swap(ch[x][0], ch[x][1]);
}
inline void pushdown(int x){if(!rev[x])return;
    rev[x] = 0; reverse(ch[x][0]); reverse(ch[x][1]);
}
inline void jie(int x, int y, int k){ch[y][k] = x; fat[x] = y;}
inline void rotate(int x, int k){
    int y = fat[x]; pushdown(y);
    jie(ch[x][k], y, !k);
    fat[x] = fat[y];
    jie(y, x, k);
    if(root[y])root[y] = 0, root[x] = 1;
    else ch[fat[x]][ch[fat[x]][1] == y] = x;
    pushup(y);
}
inline void Pushdown(int x){
    if(!root[x])Pushdown(fat[x]);
    pushdown(x);   
}
inline void splay(int x){
    Pushdown(x);
    while(!root[x]){
        int y = fat[x], ky = ch[fat[y]][1] == y;
        if(root[y]){rotate(x, ch[y][0] == x); break;}
        if(ch[y][ky] == x)rotate(y, !ky), rotate(x, !ky);
        else rotate(x, ky), rotate(x, !ky);   
    }    pushup(x);
}
inline void access(int x){
    int y = 0;
    do{
        splay(x);
        root[ch[x][1]] = 1;
        root[ch[x][1] = y] = 0;
        pushup(x);
        x = fat[y = x];
    }while(x);
}
bool same(int x, int y){
    while(fat[x])x = fat[x];
    while(fat[y])y = fat[y]; return x == y;  
}
bool makeroot(int x){
    access(x), splay(x), reverse(x);   
}
inline void link(int x, int y){
    makeroot(x); fat[x] = y;   
}
inline void cut(int x, int y){
    makeroot(x); access(y); splay(y); ch[y][0] = fat[x] = 0; root[x] = 1;   
}
int main(){
    scanf("%d%d", &n, &m);
    memset(root, 1, sizeof(root));
    for(int i = 1; i <= n; i ++)scanf("%d", &val[i]);
    while(m --){
        int k, a, b; scanf("%d%d%d", &k, &a, &b);
        if(k == 0){makeroot(a); access(b); splay(b); printf("%d\n", s[b]);}
        if(k == 1){if(!same(a, b)) link(a, b);}
        if(k == 2){if(same(a, b)) cut(a, b);}
        if(k == 3){access(a); splay(a); val[a] = b; pushup(a);}
    }
  //  system("pause");
    return 0;   
}


bzoj 2631

又是一道裸LCT >_<

现在觉得拍这种题真是爽啊,,不用思考

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 100005
#define mod 51061
using namespace std;
int n, m, jia[MAXN], cheng[MAXN], sz[MAXN], sum[MAXN], val[MAXN], ch[MAXN][2], fat[MAXN];
bool root[MAXN], rev[MAXN];
inline void reverse(int x){
	rev[x] ^= 1; swap(ch[x][0], ch[x][1]);
}
inline void calc(int x, int c, int j){
	if(!x)return;
	val[x] = (((long long)val[x] * c) + j) % mod;
	sum[x] = ((long long)sum[x] * c + (long long)sz[x] * j) % mod;
	cheng[x] = ((long long)cheng[x] * c) % mod;
	jia[x] = ((long long)jia[x] * c + j) % mod;
}
inline void pushdown(int x){
	if(rev[x]){
		rev[x] = 0;
		reverse(ch[x][0]), reverse(ch[x][1]);
	}
	if(cheng[x] != 1 || jia[x]){
		calc(ch[x][0], cheng[x], jia[x]), calc(ch[x][1], cheng[x], jia[x]);
		cheng[x] = 1, jia[x] = 0;
	} 
}
inline void pushup(int x){
	sum[x] = (sum[ch[x][0]] + sum[ch[x][1]] + val[x]) % mod;
	sz[x] = (sz[ch[x][0]] + sz[ch[x][1]] + 1) % mod;
}
inline void jie(int x, int y, int k){fat[x] = y; ch[y][k] = x;}
inline void rotate(int x, int k){
	int y = fat[x]; pushdown(y);
	jie(ch[x][k], y, !k);
	fat[x] = fat[y];
	jie(y, x, k);
	if(root[y])root[y] = 0, root[x] = 1;
	else ch[fat[x]][ch[fat[x]][1] == y] = x;
	pushup(y);
}
inline void Pushdown(int x){
	if(!root[x])Pushdown(fat[x]);
	pushdown(x);
}
inline void splay(int x){
	Pushdown(x);
	while(!root[x]){
		int y = fat[x], ky = ch[fat[y]][1] == y;
		if(root[y]){rotate(x, ch[y][0] == x)	; break;}
		if(ch[y][ky] == x)rotate(y, !ky), rotate(x, !ky);
		else rotate(x, ky), rotate(x, !ky);
	}pushup(x);
}
void access(int x){
	int y = 0;
	do{
		splay(x);
		root[ch[x][1]] = 1;
		root[ch[x][1] = y] = 0;
		pushup(x);
		x = fat[y = x];
	}while(x);
}
inline void makeroot(int x){
	access(x), splay(x), reverse(x);
}
inline void link(int x, int y){
	makeroot(x); fat[x] = y; 
}
inline void cut(int x, int y){
	makeroot(x); access(y), splay(y); fat[x] = ch[y][0] = 0; root[x] = 1;
}
int main(){
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++)root[i] = val[i] = sum[i] = sz[i] = cheng[i] = 1;
	for(int i = 1; i < n; i ++){
		int u, v; scanf("%d%d", &u, &v); link(u, v);
	}while(m --){
		char s[20]; int a, b, c, d;scanf("%s%d%d", s, &a, &b);
		if(s[0] == '-'){
			scanf("%d%d", &c, &d);
			cut(a, b); link(c, d);
		}
		if(s[0] == '/'){
			makeroot(a); access(b), splay(b);
			printf("%d\n", sum[b]);
		}
		if(s[0] == '+'){
			scanf("%d", &c);
			makeroot(a); access(b), splay(b);
			calc(b, 1, c);
		}
		if(s[0] == '*'){
			scanf("%d", &c);
			makeroot(a); access(b), splay(b);
			calc(b, c, 0);//cout<<sum[3]<<endl;
		}
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值