Top Tree
总归要学的。
先写一下理解比较困难的点。
- 考虑 SATT 的建立过程:首先在树里面找到一个Compress Tree,这个树满足中序遍历写下来是根簇路径深度从小到大排列,然后根簇路径上挂着的小簇Rake起来,这个 Rake 的过程是容易的,考虑对于每一个直接连接的小簇,把他变成子问题,然后给一个代表点(Rake Node),和子问题的 Compress Tree 的根节点连接,然后这些代表点一个一个连在一起,依照合并顺序变成 Rake Tree,我们可以认为 Rake Node 代表的是这里有一个小簇等待连边,而很明显Compress结点和Rake结点万万不能混在一起,所以我们就他们内部用二叉树各自维护平衡(使用 Splay),而中间使用三叉树,用中间结点将他们连接。
- 考虑信息的维护过程,我们已经用Rake Tree 将原树切割成了若干个 Compress Tree,也就是若干链,对于一个Compress Node,他的子树中的左右儿子代表的是他的当前子树簇路径的两个端点,而中儿子代表他挂的小簇的端点,这个是Rake下来的,考虑这个就是簇路径之外的点,于是我们可以用这个东西维护簇路径信息和簇信息,考虑我们能够通过expose强制把两个点塞根簇的端点里,也就是说我们可以这样维护路径信息,而子树信息也很好维护,因为我们可以维护子树簇内非子树簇路径的点的权值和,那我们只需要把点 x 做一次 access 之后对 x 的子树的非路径信息做操作,再合并 x 的信息即可,因为 x 是端点,端点的话子树内非簇信息就是子树啊。
- Access 操作实际上是这样的,对于一个点他从属于一条 Compress Tree,而 Compress Tree 除非根簇上面都有一个 Rake Node,那么我们需要做得操作就是首先把当前点提到 Compress Tree 的根,然后操縦 Rake Node 到 Rake Tree 的根结点,这个时候我们再把 x 的父亲的父亲提到他所在的 Compress Tree 的根结点,此时如果这个点还有右结点,我们可以直接互换,否则的话我们就把 x 直接点过去并删除当前的 Rake Node。
而原理也很易懂,考虑Compress Tree要求中序遍历下正好深度由当前钦点的同上层 Compress Tree 相连的结点往下递增,而我们做这样的操作的意思是,如果还有右端点(也就是在上一层的下端结点),就直接交换,将上一层的 Compress Node 作一个连接,相当于是将他的末尾端点引导过来,而上一层如果没有那就是延长末尾到当前簇,那当前簇就没必要再通过 Rake 操作进入树上了,可以直接进入上一层的 Compress Tree,所以我们直接删除当前的Rake结点即可。
我们都知道根簇应该有两个端点,其中一个是根结点,而 Access(x) 的作用就是使 x 成为根簇中的非根结点端点,这个很有用的,而Makeroot(x) 的作用则是把 x 变成根簇的根结点端点注意到我们是可以控制根簇端点的,着可以让我们维护很多东西! - 连边和删边,其实也很简单,我们先做一下 makeroot(x)将 x 提到根结点,再access(y)将y提到根结点,具体区别见上,然后我们现在就是要在让两个根簇连接在一起,怎么连接呢,考虑相当于是做 compress 操作,x 现在是根结点而 y 现在是末尾,而具体路径结构是怎么接都不会变的,我们的需求是让路径的开头也就是 x和路径的结尾也就是 y 直接接到一起(具体操作是 y 的右儿子接 x)这样就认为连上了边!
而基簇怎么办?基簇考虑对于Compress Tree,一个原树结点的作用是 Compress 或者是不做(端点),而 我们又知道对于一条边进入 Compress Tree 的方式只能是通过点的代表点,那这里结果就出来了,把基簇放到 x 的左儿子上,这样的话中序遍历合并整体就是对的,具体的合并是很优美的。
可惜 LCT 板子只需要我们维护点权,基簇维护与否我们根本不在乎。 - 标记下传,有点弯弯绕绕,实际上我们的标记要分类,路径标记只能传两边,子树标记能传两边和中间,而维护的信息也是分簇路径信息和非簇路径信息两种。
#include<bits/stdc++.h>
#define ls(x) ch[x][0]
#define rs(x) ch[x][1]
#define ms(x) ch[x][2]
using namespace std;
class TopTree{
public:
static const int MAXN=600005;
int ch[MAXN*3][3];
int father[MAXN*3];
int edgeval[MAXN*3];
int pathval[MAXN*3];
int val[MAXN*3];
bool revtag[MAXN*3];
int top,tot;
int trash[MAXN*3];
int newnode(int x){
int tmp;
if(top)tmp=trash[top--];
else tmp=++tot;
val[tmp]=x;
pushup(tmp);
return tmp;
}
bool nroot(int x){
return ls(father[x])==x||rs(father[x])==x;
}
int get(int x){
return ms(father[x])==x?2:rs(father[x])==x;
}
void pushup(int x){
edgeval[x]=edgeval[ls(x)]^edgeval[rs(x)]^edgeval[ms(x)]^val[x];
pathval[x]=pathval[ls(x)]^pathval[rs(x)]^val[x];//
return;
}
void rotate(int x){
int y=father[x],z=father[y];
int k=get(x);
if(z)ch[z][get(y)]=x;
ch[y][k]=ch[x][k^1];
if(ch[x][k^1])father[ch[x][k^1]]=y;
ch[x][k^1]=y;
father[y]=x;
father[x]=z;
pushup(y);
pushup(x);
return;
}
void reverse(int x){
revtag[x]^=1;
swap(ch[x][0],ch[x][1]);
return;
}
void add(int x,int w){
val[x]^=w;
pushup(x);
return;
}
void pushdown(int x){
if(!revtag[x])return;
reverse(ch[x][0]),reverse(ch[x][1]);
revtag[x]=false;
return;
}
void update(int cur){
if(nroot(cur))update(father[cur]);
pushdown(cur);
return;
}
void splay(int x,int goal=0){
update(x);
for(int now=father[x];now=father[x],nroot(x)&&(father[x]!=goal);rotate(x)){
if(nroot(now)&&father[now]!=goal){
if(get(now)==get(x)){
rotate(now);
}
else{
rotate(x);
}
}
}
pushup(x);
return;
}
void clear(int &cur){
ch[cur][0]=ch[cur][1]=ch[cur][2]=0;
val[cur]=0;
revtag[cur]=0;
father[cur]=0;
edgeval[cur]=0;
pathval[cur]=0;
trash[++tot]=cur;
cur=0;
return;
}
void setthefather(int x,int fa,int type){
if(x)father[x]=fa;
ch[fa][type]=x;
return;
}
void del(int x){
if(!ch[x][0]){
setthefather(rs(x),father[x],2);
clear(x);
return;
}
int now=ls(x);
pushdown(now);
while(rs(now)){
now=rs(now);
pushdown(now);
}
splay(now,x);
setthefather(rs(x),now,1);
setthefather(now,father[x],2);
pushup(now);
pushup(father[x]);
clear(x);
return;
}
void Splice(int x){
splay(x);
int y=father[x];
splay(y);
pushdown(x);
if(rs(y)){
swap(father[rs(y)],father[ms(x)]);
swap(rs(y),ms(x));
pushup(x);
}
else{
setthefather(ms(x),father[x],1);
del(x);
}
pushup(rs(y));
pushup(y);
return;
}
void access(int x){
splay(x);
int tmp=x;
if(rs(x)){
int y=newnode(0);
setthefather(rs(x),y,2);
setthefather(ms(x),y,0);
setthefather(y,x,2);
rs(x)=0;
pushup(y);
pushup(x);
}
while(father[x]){
Splice(father[x]);
x=father[x];
}
splay(tmp);
return;
}
void makeroot(int x){
access(x);
reverse(x);
return;
}
void expose(int x,int y){
makeroot(x);
access(y);
return;
}
int findroot(int x){
access(x);
pushdown(x);
while(ls(x))x=ls(x),pushdown(x);
splay(x);
return x;
}
void link(int x,int y){
if(findroot(x)==findroot(y))return;
expose(x,y);
int z=newnode(0);
setthefather(x,y,1);
setthefather(z,x,0);
pushup(x);
pushup(y);
return;
}
void cut(int x,int y){
expose(x,y);
if(ls(y)!=x||father[x]!=y||ls(rs(x))||ms(rs(x))||rs(rs(x))){
return;
}
clear(rs(x));
father[x]=0;
ls(y)=0;
pushup(y);
pushup(x);
return;
}
}P;
int n,m;
int main(){
// freopen("test.in","r",stdin);
// freopen("test.out","w",stdout);
scanf("%d%d",&n,&m);
P.tot=n;
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
P.val[i]=x;
}
while(m--){
int opt,x,y;
scanf("%d%d%d",&opt,&x,&y);
switch(opt){
case 0:{
P.expose(x,y);
printf("%d\n",P.pathval[y]);
break;
}
case 1:{
P.link(x,y);
break;
}
case 2:{
P.cut(x,y);
break;
}
case 3:{
P.access(x);
P.val[x]=y;
P.pushup(x);
break;
}
}
}
return 0;
}
一点分讨都没有的动态树模板代码,维护了基簇与子树簇路径的答案和非子树簇路径的答案。