简介
一般的树上带修改问题,树剖(轻重链剖分)就可以搞定了,但是万一有连边、断边之类的东西?Good Game.
我们想一想,什么数据结构能处理移动点的问题?平衡树。
那我们就尝试把平衡树挪到树上,这时我们的树剖方式就叫做实链剖分。
定义与性质
先给定义。
实边:包含在一个splay中的边。一条实边恰好包含在一个splay中。一个节点的儿子只有一个连的是实边,其它都是虚边。
虚边:由一个splay的某个节点拉向另一个splay的根节点的边。
实链:一个splay里面从上到下连续的实边(感性理解一下,我说的不太清楚也不太准确)。
然后给出一些重要性质。
1、LCT由很多splay组成,这些splay之间由虚边连接。每个splay中序遍历得到的点,在原树中都按深度从小到大严格升序。
2、(此处的父亲,代表splay中的父子关系,并不是原树的)对于实边,父节点的儿子就是子节点,子节点的父亲就是父节点。但是对于虚边,子节点肯定是一个splay的根,父节点肯定是另一个splay的某个节点,所以通过父节点不能访问到子节点,但通过子节点能访问到父节点,这就是“认父不认子”。利用该性质可以判断一个点是不是splay的根。
操作
核心操作
access
这是核心中的战斗机,就是把某个点到原树根节点的路径全部拉成一条实链,且这条实链要以该点结尾。我看网上的讲解都写得很简洁,但不知道为什么scarlyw大佬的access函数写得忒复杂,还需要另外两个函数来铺垫,我这里就不提了,因为我没学会她的神奇写法(其实是我没听讲+我太菜了)。
代码
inline void access(int x) {for(register int y=0;x;y=x,x=ft[x]) splay(x),ch[x][1]=y,pushup(x);}
makeroot
把某个点钦定为原树的根节点,需要先access一下,再把这个splay从头到尾翻转一下,这样你会发现该splay中所有的点在原树中的深度都颠倒过来了,但是这条链旁侧的分支毫无影响,于是原先在原树最下方深度最大的钦定点就变成深度最小的原树根节点。是不是很巧妙啊?
代码
inline void makeroot(int x) {access(x),splay(x),pushrev(x);}
findroot
查找该点所在原树的根节点,只需access一下,把该点转为该splay的根,接着一直找左儿子,最后找到的就是原树根节点。
代码
inline int findroot(int x) {access(x),splay(x);while(ch[x][0]) x=ch[x][0];splay(x);return x;}
其他操作
有了这个,现在很多树上操作都是小事一桩。
连、断边
首先要用findroot操作判断连、断边是否合法。当然,如果没有断边操作,可以并查集,常数更小。
连边只需把一个点makeroot一下,再接到另一个点下面。
断边只需把其中一个点makeroot一下,再把原树中的父亲的儿子和儿子的父亲设为0即可,要pushup。
inline void link(int x,int y) {makeroot(x);if(findroot(y)!=x) ft[x]=y;}
inline void cut(int x,int y) {makeroot(x);if(findroot(y)==x&&ft[y]==x&&!ch[y][0]) ft[y]=ch[x][1]=0,pushup(x);}
提取一条链
此处以查询一条链的点权异或值为例。我们一个点makeroot一下,另一个点access一下,这条链就提出来了。
inline int qxor(int x,int y) {makeroot(x),access(y),splay(y);return val[y];}
FBI Warning
这里我们用的splay与一般写法有点小小的差别。
首先,我们要自行写函数判断一个点是否为splay的根,在旋转函数中你会遇到。
接着,在splay时,要先从splay的根开始沿着这条链一直往下pushdown。为什么?也许是我们旋转时并未pushdown的缘故,但到底怎么回事我不知道,毕竟我没听scarlyw大佬讲课+我太菜了。
代码
究极压行,但fsy大佬压行比我的厉害多了
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+50;
int n,m,a[MAXN],pstk[MAXN];
int ch[MAXN][2],val[MAXN],ft[MAXN],rev[MAXN];
inline int Read()
{
int x=0,f=1;char ch;
while(!isdigit(ch=getchar())) if(ch=='-') f=-1;
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
inline void pushup(int x) {val[x]=a[x]^val[ch[x][0]]^val[ch[x][1]];}
inline void pushrev(int x) {if(x) rev[x]^=1,swap(ch[x][0],ch[x][1]);}
inline void pushdown(int x) {if(rev[x]) pushrev(ch[x][0]),pushrev(ch[x][1]),rev[x]=0;}
inline int isroot(int x) {return ch[ft[x]][0]!=x&&ch[ft[x]][1]!=x;}
inline void rota(int x)
{
int y=ft[x],z=ft[y],k=(x==ch[y][1]);
ft[x]=z;if(!isroot(y)) ch[z][y==ch[z][1]]=x;
ft[ch[x][k^1]]=y,ch[y][k]=ch[x][k^1];
ft[y]=x,ch[x][k^1]=y;
pushup(x),pushup(y);
}
inline void splay(int x)
{
int top=0,y=x,z;
pstk[++top]=y;
while(!isroot(y)) pstk[++top]=y=ft[y];
while(top) pushdown(pstk[top--]);
while(!isroot(x))
{
y=ft[x],z=ft[y];
if(!isroot(y)) (x==ch[y][1])^(y==ch[z][1])?rota(x):rota(y);
rota(x);
}
pushup(x);
}
inline void access(int x) {for(register int y=0;x;y=x,x=ft[x]) splay(x),ch[x][1]=y,pushup(x);}
inline int findroot(int x) {access(x),splay(x);while(ch[x][0]) x=ch[x][0];splay(x);return x;}
inline void makeroot(int x) {access(x),splay(x),pushrev(x);}
inline void link(int x,int y) {makeroot(x);if(findroot(y)!=x) ft[x]=y;}
inline void cut(int x,int y) {makeroot(x);if(findroot(y)==x&&ft[y]==x&&!ch[y][0]) ft[y]=ch[x][1]=0,pushup(x);}
inline int qxor(int x,int y) {makeroot(x),access(y),splay(y);return val[y];}
int main()
{
n=Read(),m=Read();
for(register int i=1;i<=n;++i) a[i]=Read();
int opt,x,y;
for(register int i=1;i<=m;++i)
{
opt=Read(),x=Read(),y=Read();
if(opt==0) printf("%d\n",qxor(x,y));
else if(opt==1) link(x,y);
else if(opt==2) cut(x,y);
else splay(x),a[x]=y,pushup(x);
}
return 0;
}
原文地址:https://www.cnblogs.com/SKTT1Faker/p/12116315.html