之前说了Splay Tree 和 树链剖分,其实都是为今天Link Cut Tree 做铺垫,在这里首先要膜拜鼻祖杨哲大牛orz……
杨哲——《QTREE解法的一些研究》:点击打开链接 这个是官方论文的介绍。
我在学习的时候在网上看到,有人说Link Cut Tree其实是树链剖分+Splay,但是学完之后发现这个说法其实并不妥,首先还是一样介绍一下几个概念:
1、preferred path:在访问(access)一个点后,从根节点到该点的路径(链)称为preferred path
2、preferred chrild:对于一个节点,若它的一个儿子在preferred path上,则这个儿子就是preferred chrild
3、Auxiliary Tree:对于每一条preferred path,我们都用一棵树来维护它的性质,这样的树称为辅助树(Auxiliary Tree)
介绍完这几个概念后,你可能发现了Link Cut Tree与树链剖分的相似之处,就是每一条preferred path相当于一条树链,然后经过一次次的access,也就把一棵树剖分成了很多条preferred path(链)。但是我想说,看起来想,但是Link Cut Tree的剖分方式并不是按照轻重儿子去剖,而是单纯的看access的方式,而且如果是access(i)那么,这条preferred path并不会包含i的儿子,这是与树链剖分不同的地方。
既然如此,可能你会说,既然有了树链剖分,那么还要Link Cut Tree干嘛?告诉你,Link Cut Tree顾名思义,是一个可以Link也可以Cut的Tree。具体点就是,树链剖分相当于只是一个静态的问题,它的树的结构(父子关系)不会变,也不会要求添加或者删除某一条边,而Link Cut Tree可支持动态的改变树的结构,可以任意对树进行切断和重(chong)连。这个怎么做到的呢?别急,慢慢来……
然后,我们再说说这个Auxiliary Tree。一般来说我们选择Splay Tree来维护一条链。一开始,每个点独立地属于一棵Splay Tree(我当时就想,这样不会MLE吗?其实这可以很巧妙的解决,后面会说)。接下来access后便把这些Auxiliary Tree合并了。
首先,说说最重要的access操作,Link Cut Tree精髓所在。access(i),即访问i这个节点,同时把从根一直到改点的路径标记为preferred path,并把这路径上的点交给一棵Auxiliary Tree来维护。我初学时就纳闷,一个节点即属于原树,又属于Auxiliary Tree,它的父亲到底是那棵树里的父亲,若为Auxiliary Tree的父亲,是否会丢掉原来的关系?其实,不必纠结。在原树中,我们只保存某个节点的父亲而不保存儿子,当该点进入Auxiliary Tree后才保存左右儿子的信息。然后设置root[]数组,若root[i]==1则代表点i是一棵Auxiliary Tree的根。对于每一个Splay Tree,我们约定,深度:左儿子<父亲<右儿子。access(i)时,先splay(i),把i转到它所在的Auxiliary Tree的根(当然一开始它已经是根)。然后把根变成i的右儿子,因为preferred path不包括i的儿子。接着,更新右儿子,然后继续往上走,一直走到原树的根。
粗线表示preferred path,左边为开始状态,右边为access(N)之后。可以看见,从根A到N所有的边都被标记为粗,在代码中就表现为A的右儿子是C,C的右儿子是G,G的右儿子是H……然后原来不是Auxiliary Tree的根的K、J、O都变成了根,具体见代码,这个还是不容易理解的,只能说到这了:
inline void access(int x) //仅仅只是标记preferred path,并没有把x变为根,若要把x变为根,加一个splay(x)
{
int y=0;
while (x) //没到原树的根就一直做
{
splay(x); //把x变为Auxiliary Tree的根
root[tree[x].r]=1; //把右儿子标记为根
tree[x].r=y; //更新儿子
root[y]=0; //path的其它点不为根
y=x; //往上继续走
x=tree[x].fa;
}
}
然后Link Cut Tree中rotate操作与splay操作也有所不同:
inline void rotate(int x)
{
int fa=tree[x].fa,grand=tree[fa].fa;
if (get(x))
{
tree[fa].r=tree[x].l;
tree[tree[fa].r].fa=fa;
tree[x].l=fa;
} else
{
tree[fa].l=tree[x].r;
tree[tree[fa].l].fa=fa;
tree[x].r=fa;
}
tree[x].fa=grand;
tree[fa].fa=x;
if (grand)
{
if (tree[grand].r==fa) tree[grand].r=x;
else if (tree[grand].l==fa) tree[grand].l=x; //用else if不能直接用else,因为原树不一定是二叉树
}
root[x]=root[x] xor root[fa]; //用xor快速交换root的值
root[fa]=root[x] xor root[fa]; //根的传递
update(fa);
update(x);
}
inline void splay(int x)
{
while (!root[x]) rotate(x); //当x为Auxiliary Tree的根的时候停止
}
接下来是最感兴趣的Link和Cut。Link:
inline void link(int x,int y)
{
access(x);
splay(x);
reverse(tree[x].l);
access(y);
splay(y);
tree[y].r=x;
tree[x].fa=y;
root[x]=0;}
inline void cut(int x)
{
access(x);
splay(x);//旋转x点到根节点
tree[tree[x].l].fa=0;
root[tree[x].l]=true;//设置左子树根节点
tree[x].l=-1;
}
这题对于一个弹簧,若他在位置i,弹力为k,则连边(i,i+k),若i+k>n+1,则连边(i,n+1),最后就会形成一棵以n+1为根的树。然后询问在某个位置经过几次弹射能到n+1之外,那么问题就转化为了求该点在这棵树的深度。然后,对于修改,则正好可用Link和Cut来完成。
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iomanip>
#define LL long long
#define INF 2147483647
#define ENT putchar('\n')
#define up(_i,_a,_b) for (int _i=_a;_i<=_b;_i++)
#define down(_i,_a,_b) for (int _i=_a;_i>=_b;_i--)
#define efo(_i,_a) for (int _i=ls[_a];_i!=0;_i=g[_i].next)
#define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout);
using namespace std;
inline int read()
{
LL f=1,d=0;char s=getchar();
while (s<'0'||s>'9'){if (s=='-') f=-1;s=getchar();}
while (s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
return f*d;
}
inline LL readln()
{
LL f=1,d=0;char s=getchar();
while (s<'0'||s>'9'){if (s=='-') f=-1;s=getchar();}
while (s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
while (s!='\n') s=getchar();
return f*d;
}
inline void write(LL x)
{
if(x==0){putchar('0');return;}if(x<0)putchar('-'),x=-x;
int len=0,buf[20];while(x)buf[len++]=x%10,x/=10;
int i=len-1; while (i>=0) {putchar(buf[i--]+'0');}return;
}
inline void writeln(LL x) {write(x);ENT;}
struct node
{
int l,r,fa,num,cnt,size; //cntΪ¸Ã¹Ø¼ü×Ö³öÏÖ´ÎÊý£¬sizeΪ×ÓÊ÷´óС
} tree[200010];
bool root[200010];
int sz; //szΪÕû¿ÃÊ÷µÄ´óС
inline int get(int x) //¿´xÊÇ×ó¶ù×Ó»¹ÊÇÓÒ¶ù×Ó
{
return tree[tree[x].fa].r==x;
}
inline void update(int x) //¸üÐÂ×ÓÊ÷µÄsize
{
if (x)
{
tree[x].size=tree[x].cnt;
if (tree[x].l) tree[x].size+=tree[tree[x].l].size;
if (tree[x].r) tree[x].size+=tree[tree[x].r].size;
}
}
inline void rotate(int x)
{
int fa=tree[x].fa,grand=tree[fa].fa;
if (get(x))
{
tree[fa].r=tree[x].l;
tree[tree[fa].r].fa=fa;
tree[x].l=fa;
} else
{
tree[fa].l=tree[x].r;
tree[tree[fa].l].fa=fa;
tree[x].r=fa;
}
tree[x].fa=grand;
tree[fa].fa=x;
if (grand)
{
if (tree[grand].r==fa) tree[grand].r=x;
else if (tree[grand].l==fa) tree[grand].l=x; //ÓÃelse if²»ÄÜÖ±½ÓÓÃelse£¬ÒòΪÔÊ÷²»Ò»¶¨ÊǶþ²æÊ÷
}
root[x]=root[x] xor root[fa]; //ÓÃxor¿ìËÙ½»»»rootµÄÖµ
root[fa]=root[x] xor root[fa];
update(fa);
update(x);
}
inline void splay(int x)
{
while (!root[x]) rotate(x);
}
inline void access(int x)
{
int y=0;
while (x)
{
splay(x);
root[tree[x].r]=1;
tree[x].r=y;
root[y]=0;
y=x;
x=tree[x].fa;
}
}
int main()
{
int n,m;
n=read();
up(i,1,n)
{
tree[i].fa=min(read()+i,n+1);
tree[i].size=tree[i].cnt=1;
}
tree[n+1].size=tree[n+1].cnt=1;
memset(root,1,sizeof(root));
m=read();
up(i,1,m)
{
int q,x,k;
q=read();
x=read()+1;
if (q==1)
{
access(x);
splay(x);
writeln(tree[tree[x].l].size);
} else
{
k=read();
splay(x);
tree[tree[x].l].fa=tree[x].fa;
root[tree[x].l]=1; //reverseʱ±ðÍüÁ˰Ѷù×Ó±ê¼ÇΪ¸ù
tree[x].l=0;
update(x);
tree[x].fa=min(k+x,n+1);
}
}
}