推荐资料:
《SPOJ375 QTREE 解法的一些研究》by Yang Zhe
《link cut tree》by popoqqq
正文:
LCT 是解决动态树问题的一种数据结构
LCT=树链剖分+splay
LCT利用splay来维护树上的树链,但是树链不能再以size来剖分了,否则树是静态的。
LCT利用Access操作,将需要访问的节点合并成一个splay,再进行操作。
在splay中是以点的深度作为关键字的。
为了更好维护这一堆splay,需要引入一个叫做Auxiliary Tree(辅助树)的东西。
在辅助树中,如果两个节点u,v,u认为v是父亲,而v不认u是儿子,那么v就是一个splay的根,这条边就叫虚边。
所以这就解释了下面Access中为什么只更新父亲节点的儿子信息,而没有更新父亲节点原来Preferred Child的父亲信息(这一点很精妙,仔细体会)。
具体函数
Access(x):这是一切操作的根源,把x到根的路径上的所有点都合成一个splay,方便操作。
make_root(x):将x设为原树的根
make_root(5)就像如图所示:
可以发现,除了5到根的路径上的节点深度发生了翻转,别的节点深度相对关系是不变的,所以在splay上将5到根的路径的所有点翻转一下。
find(x):找到x的根。由于是按深度为关键字,所以把x旋成x所在的splay的根,再一直向左儿子找,没有了左儿子之后这个点就是根。
Link(x,y):连接x,y。先将x旋成原树的根,再把fa[x]设成y(注意x的儿子不用更新,这是一条虚边)。
Cut(x,y):断开x,y的连边。先将x旋成原树的根,再Access(y),Splay(y),于是x在y左儿子,切断。
维护其他x到y路径上的信息,只要make_root(x),Access(y),Splay(y)就好了。
代码
就拿hdu4010 Query on the tree了:
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
const int INF=2000000000;
const int M=300005;
int n;
int tag[M],rev[M],c[M][2],fa[M],mx[M],v[M];
struct Edge{
int to,nxt;
}edge[M<<1];
int T,head[M],stk[M];
void init(){
for(int i=0;i<=n;i++)
tag[i]=rev[i]=fa[i]=c[i][0]=c[i][1]=0,head[i]=-1;
mx[0]=-INF;T=0;
}
void add_edge(int a,int b){
edge[T]=(Edge){b,head[a]};
head[a]=T++;
edge[T]=(Edge){a,head[b]};
head[b]=T++;
}
void rec(int x,int f){
fa[x]=f;
for(int i=head[x];~i;i=edge[i].nxt)
if(edge[i].to!=f)
rec(edge[i].to,x);
}
struct Link_Cut_Tree{
bool is_root(int x){
return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;
}
void push_up(int x){
int l=c[x][0],r=c[x][1];
mx[x]=max(mx[l],mx[r]);
mx[x]=max(mx[x],v[x]);
}
void update(int x,int w){
tag[x]+=w;
mx[x]+=w;
v[x]+=w;
}
void push_down(int x){
int l=c[x][0],r=c[x][1];
if(rev[x]){
rev[l]^=1;
rev[r]^=1;
rev[x]^=1;
swap(c[x][0],c[x][1]);
}
if(tag[x]){
if(l) update(l,tag[x]);
if(r) update(r,tag[x]);
tag[x]=0;
}
}
void Rotate(int x){
int y=fa[x],z=fa[y],l,r;
l=(c[y][1]==x);
r=l^1;
if(!is_root(y)) c[z][c[z][1]==y]=x;
fa[x]=z;
fa[y]=x;
fa[c[x][r]]=y;
c[y][l]=c[x][r];
c[x][r]=y;
push_up(y);push_up(x);
}
void Splay(int x){
int top=0;
stk[++top]=x;
for(int i=x;!is_root(i);i=fa[i])
stk[++top]=fa[i];
while(top) push_down(stk[top--]);
while(!is_root(x)){
int y=fa[x];
int z=fa[y];
if(!is_root(y)){
if(c[y][0]==x^c[z][0]==y) Rotate(x);
else Rotate(y);
}
Rotate(x);
}
}
void solve(int x,int y){
make_root(x);
Access(y);
Splay(y);
}
void Access(int x){
for(int t=0;x;t=x,x=fa[x])
Splay(x),c[x][1]=t,push_up(x);
}
void make_root(int x){
Access(x);
Splay(x);
rev[x]^=1;
}
void link(int x,int y){
make_root(x);
fa[x]=y;
}
void cut(int x,int y){
make_root(x);
Access(y);
Splay(y);
c[y][0]=fa[c[y][0]]=0;
push_up(y);
}
int find(int x){
Access(x);
Splay(x);
while(c[x][0]) x=c[x][0];
return x;
}
void add(int x,int y,int val){
make_root(x);
Access(y);
Splay(y);
tag[y]+=val;
mx[y]+=val;
v[y]+=val;
}
}lct;
inline void Rd(int&res){
res=0;char c;
while(c=getchar(),!isdigit(c));
do res=(res<<1)+(res<<3)+(c^48);
while(c=getchar(),isdigit(c));
}
int main(){
while(scanf("%d",&n)!=EOF){
init();
for(int i=1;i<n;i++){
int a,b;
Rd(a);Rd(b);
add_edge(a,b);
}
for(int i=1;i<=n;i++)
Rd(v[i]),mx[i]=v[i];
rec(1,0);
int m;
scanf("%d",&m);
while(m--){
int opt,x,y;
Rd(opt);Rd(x);Rd(y);
if(opt==1){
if(lct.find(x)==lct.find(y)) puts("-1");
else lct.link(x,y);
}else if(opt==2){
if(lct.find(x)!=lct.find(y)||x==y) puts("-1");
else lct.cut(x,y);
}else if(opt==3){
int z=x;
x=y;
Rd(y);
if(lct.find(x)!=lct.find(y)) puts("-1");
else lct.add(x,y,z);
}else{
if(lct.find(x)!=lct.find(y)) puts("-1");
else{
lct.solve(x,y);
printf("%d\n",mx[y]);
}
}
}
puts("");
}
return 0;
}