您的位置:首页 > 其它

洛谷3690 link-cut-tree模板 动态树

2018-03-29 20:48 337 查看
题目描述

给定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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: