推薦資料:
《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;
}