洛谷3690 link-cut-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。

link-cut-tree的模板题目,似乎网上写这道题的题解的人不是很多,我就发一个这个题的题解。由于我新学link-cut-tree,介绍或者注释中有什么不正确的地方还请多多斧正。

如果你学过link-cut-tree的思想的话应该看看我代码就能看懂这个该怎么实现,lct我这里不做讲解。

模板题也没什么太多的东西要说的,就是注意一下什么时候该更新就好了。

直接看代码吧,代码我写了不少注释。

#include <bits/stdc++.h>
using namespace std;
//注意什么时候要reverse,什么时候要pushup,什么时候要pushdown 
int n,m,f[300001],c[300001][2],v[300001],s[300001],st[300001];
//v是当前点的权值,s是异或和,st后面有说明, f为当前点的父节点,c分别是当前点的左右儿子 
int rev[300001];//翻转标记 
void pushup(int x)
{
	s[x]=s[c[x][0]]^v[x]^s[c[x][1]];
}
void reverse(int x)//翻转操作 
{
	swap(c[x][0],c[x][1]);
	rev[x]^=1;
}
int nroot(int x)//判断节点是否为一个Splay的根(与普通Splay的区别1)
{
	//如果是根返回0,不是根返回1 
	return c[f[x]][0]==x||c[f[x]][1]==x;
}
void pushdown(int x)//判断并释放标记 
{
	if(rev[x])
	{
		if(c[x][0])
		reverse(c[x][0]);
		if(c[x][1])
		reverse(c[x][1]);
		rev[x]=0;
	}
}
void rotate(int x)
{
	int y=f[x],z=f[y],k=c[y][1]==x,w=c[x][!k];
//先更新儿子	
	if(nroot(y))
	c[z][c[z][1]==y]=x;
	c[x][!k]=y;
	c[y][k]=w;
//再更新父亲	
	if(w)
	f[w]=y;
	f[y]=x;
	f[x]=z;
	pushup(y);
}
void splay(int x)//只传一个参数,因为目标都是变成该splay的根(与普通Splay的区别3) 
{
	int y=x,z=0;
	st[++z]=y;
//st为栈,暂存当前点到根的整条路径,pushdown时一定要从上往下放标记(与普通Splay的区别4)
	while(nroot(y))
	st[++z]=y=f[y];
	while(z)
	pushdown(st[z--]);
	while(nroot(x))
	{
		y=f[x];z=f[y];
		if(nroot(y))
		{
			if(c[z][0]==y ^ c[y][0]==x)
			rotate(x);
			else
			rotate(y);
		}
		rotate(x);
	}
	pushup(x); 
}
void access(int x)//访问x,将x变为重儿子 
{
	int y=0;
	while(x!=0)
	{
		splay(x);//把x变为当前splay的根
//此时所有在原lct上深度比x大的点都在x的右子树上,只要把右子树断开就可以变为轻边
//(子认父但父不认子)	
		c[x][1]=y;//y成为新的重儿子 
		if(y!=0)
		f[y]=x;
		y=x;//更新y 
		x=f[x];//跳到深度更浅的一棵splay	 
	}
}
void makeroot(int x)//把x变为根 
{
	access(x);
	splay(x);
	reverse(x);
}
void split(int x,int y)//提取x到y的路径并把y变为根 
{
	makeroot(x);
	access(y);
	splay(y);
}
int findroot(int x)//找x在真实树(lct)中的根 
{
//真实树中的根在splay之后由于深度最小,位于x左子树的最左端	
	access(x);
	splay(x);
	while(c[x][0])
	{
		pushdown(c[x][0]);
		x=c[x][0];
	}
//	splay(x);//会出错?势能分析的要求?在这里写了会错 
	return x;
}
void link(int x,int y)//连边 
{
	makeroot(x);
	if(findroot(y)!=x)
	f[x]=y;
}
void cut(int x,int y)
{
/*先判一下连通性,再看看x,y是否有父子关系,还要看x是否有右儿子
因为access(y)以后,假如y与x在同一Splay中而没有直接连边,
那么这条路径上就一定会有其它点,在中序遍历序列中的位置会介于x与y之间。
那么可能x的父亲就不是y了
也可能x的父亲还是y,那么其它的点就在x的右子树中
只有三个条件都满足,才可以断掉
*/	
//注意别忘了!c[x][1] 
	makeroot(x);
	if(findroot(y)==x&&f[x]==y&&!c[x][1])
	{
		f[x]=c[y][0]=0;
		pushup(y);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	scanf("%d",&v[i]);
	int z,x,y;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&z,&x,&y);
		if(z==0)
		{
			split(x,y);
			printf("%d\n",s[y]);
		}
		if(z==1)
		link(x,y);
		if(z==2)
		cut(x,y);
		if(z==3)
		{
			splay(x);
			v[x]=y;
//先把x转上去再改,不然会影响Splay信息的正确性
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/79748735
今日推荐