题目描述
给定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; }