动态树LCT

#include<bits/stdc++.h>
using namespace std;
const int N = 330000;
int s[N], val[N], ch[N][2], rev[N], st[N], fa[N], top;
bool wh(int p) {return ch[fa[p]][1] == p;}//返回p是不是其父亲的右儿子
bool Isroot(int p) {return ch[fa[p]][wh(p)] != p;}//若p不是其父亲的左右儿子那它就是根
void Update(int p) {s[p] = val[p] ^ s[ch[p][0]] ^ s[ch[p][1]];}//S及其子树的异或和值为本身值及其左右子箱的异或和值
void Pushdown(int p) {//把P点往下的左右子树互换位置
	if(rev[p]) {//如果P结点的rev被异或为1表示其子树左右儿子要互换
		rev[p] ^= 1; //先异或回0,互换完成
		swap(ch[p][0], ch[p][1]);//交换左右儿子编号
		rev[ch[p][1]] ^= 1; //右儿子待更新上标记
		rev[ch[p][0]] ^= 1;//左儿子待更新上标记
	}
}
void Pushup(int p) {//把P点往上推(就是把P到根的路径上的点左右子树互换),仅在每次SPLAY时会调用
	top = 0; st[++top] = p;//top表示栈顶,栈中有多少元素,先把P结点压进栈里
	for(int i = p; !Isroot(i); i = fa[i]) st[++top] = fa[i];//I坐P往上走,只要不是根,它老豆就压进栈里,实即得到了到根的路径
	for(int i = top; i; --i) Pushdown(st[i]);//I从栈顶往下把栈顶元素弹出,并从该点开始往下交换子树
}
void Rotate(int p) {//对P点旋转(比伸展树简洁没AVL树那么多种情况)
	int f = fa[p], g = fa[f], c = wh(p);//读出P老豆,P祖父,P是其老豆的左还是右儿
	//一、更新当前P与祖父G的指针
	if(!Isroot(f)) ch[g][wh(f)] = p; //老豆不是根即存在祖父,则当前点变为祖父指向老豆的那条儿子边
	fa[p] = g;//然后记祖父为当前点的父亲(老豆是根则祖父不存在,本结点变成根也是老豆不存在的,反正就是顶了老豆位置)
	//二、更新当前P的c^1边儿子与父亲F的指针
	ch[f][c] = ch[p][c ^ 1]; //然后原来老豆指向当前结点的那条边指向了当前结点的与C相反的那条边指向的结点(见图)
	if(ch[f][c]) fa[ch[f][c]] = f;//如果P的c^1边儿子存在就把其父更新为f
	//三、更新当前结点P与原父亲F的指针
	ch[p][c ^ 1] = f;//原来的父亲变成当前结点的c^1边儿子
	fa[f] = p;//当前结点变成原来父新的父亲
	//f变小了所以对应其控制的子树st异或和也应该更新
	Update(f);
}
void Splay(int p) {//把P移到树根(连上ROTATE操作就是伸展树了)
	Pushup(p);//每说移动都把P到根的路径上结点的左右子树互换一下,好像说是要维护深度关系不变
	for(; !Isroot(p); Rotate(p))//一直把当前点P转到根位置为止
		if(!Isroot(fa[p]))//老豆还不是根
            Rotate(wh(fa[p]) == wh(p) ? fa[p] : p);//如果是LL,RR型转一下fa[p],否则就转一下p,每次循环再转一下P,同AVL
	Update(p);//转到根之后就更新P控制的子树的值
}
void Access(int p) {//P点到整棵LCT的大根打通,中间放弃一些子树的右儿子无影响,因为它们的FA仍在,下一欠ACCESS时能回来!
	for(int pre = 0; p; pre = p, p = fa[p]) {//SPLAY中Isroot判断他不是老爸的儿子(新LINK的边)就会认为是根
		Splay(p);//把P移到树根,每一次SPLAY时权值都会有UPDATE,需要的值已维护好
		ch[p][1] = pre;//把pre结点记为p的右儿,看明显是只取右儿子边为偏爱边
		Update(p);//然后更新P结点的值,此时S[p]就仅指其本身与其左子树的异或和
	}
}
void Makeroot(int p) {Access(p); Splay(p); rev[p] ^= 1;}//打通,提到根,提到根后要再加一次翻转标记
int Find(int p) {for(Access(p), Splay(p); ch[p][0]; p = ch[p][0]) ; return p;}//打通,提到根,找最左的儿子
void Cut(int u, int v) {Makeroot(u); Access(v); Splay(v); ch[v][0] = fa[u] = 0;}//u变成根,打通V提到根,至此保证U是V的左儿子,故清掉V左儿与U父亲
void Link(int u, int v) {Makeroot(u); fa[u] = v;}//U提到根,它老豆是V
void Change(int u, int v) {Access(u); Splay(u); val[u] = v; Update(u);}//打通U,提到根,U的值改为V,更新其S值
void Query(int u, int v) {Makeroot(u); Access(v); Splay(v); printf("%d\n", s[v]);}//U提到根,提通V提到根,至此保证U是V的左儿子,s[v]就是此偏爱路径上结点异或和值,只有U与V
int main(){
	int n,m;cin>>n>>m;//输入N个数,M个操作
	for(int i=1;i<=n;++i)cin>>s[i],val[i]=s[i];//N个结点的值
	while(m--){
		int opt,x,y;cin>>opt>>x>>y;
		if(opt == 0) Query(x, y);//询问从x到y的路径上的点的权值的xor和。保证x到y是联通的。
		if(opt == 1 && Find(x) != Find(y)) Link(x, y);//代表连接x到y,若x到Y已经联通则无需连接。
		if(opt == 2 && Find(x) == Find(y)) Cut(x, y);//代表删除边(x,y),不保证边(x,y)存在。
		if(opt == 3) Change(x, y);//代表将点X上的权值变成Y。
	}
    return 0;
}


Description
大视野在线测评:3282: Tree
给定N个点以及每个点的权值,要你处理接下来的M个操作。
操作有4种。操作从0到3编号。点从1到N编号。
0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和。
保证x到y是联通的。
1:后接两个整数(x,y),代表连接x到y,若x到Y已经联通则无需连接。
2:后接两个整数(x,y),代表删除边(x,y),不保证边(x,y)存在。
3:后接两个整数(x,y),代表将点X上的权值变成Y。
Sample Input
3 3 
1
2
3
1 1 2

0 1 2 
0 1 1
Sample Output
3
1

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值