1. 程式人生 > >關於樹論【動態樹問題(LCT)】

關於樹論【動態樹問題(LCT)】

spa cnblogs 註定 ont ++ 方法 scanf tree edge

搬運:看一道caioj1439

題目描述
一開始給你一棵n個點n-1條邊的樹,每個點有一個權值wi。
三種操作:
op=1 u v :在點u和點v之間建一條邊。
op=2 u v:摧毀點u到點v之間的邊。
op=3 w u v:將點u和點v之間路徑上的點(包括u,v),權值增加w。
op=4 u v:詢問點u到點v之間路徑上的點(包括u,v),權值最大值。
當操作違法時(詢問一中u,v已經相連,二三四中u,v不聯通,一二操作u==v)不進行操作並輸出-1。
輸入
從文件weight.in中讀取輸入。
第1行為1個正整數n,表示點的個數。
第2~n行為開始樹所有的邊,每行兩個正整數u,v,代表u和v之間有一條邊。


第n+1行有n個正整數,表示一開始點的權值。
下一行為一個正整數m代表下來有m個操作。
以下m行,一行表示一個操作。
行首先輸入一個正整數op。
當op=1,2,4時,輸入兩個正整數u,v
當op=3時,輸入三個正整數w,u,v
操作如題意
輸出
輸出到文件weight.out。
對每個4操作,輸出點u到點v之間路徑上的點(包括u,v),權值最大值。同時對於違法情況輸出-1。

該類動態樹問題一個突出點就是動態,假如沒有1、2操作當然可以方便的運用樹鏈剖分算法水過(詳見第8章 樹鏈剖分)。前一章的伸展樹只支持改變樹的形態,難以對樹的結構進行改變,對於建邊刪邊的操作的需要,我們要運用多棵伸展樹組成新樹,即解決該類動態樹問題的普遍方法,Link-Cut-Tree,俗稱LCT。


它跟樹鏈剖分類似,只不過樹剖用線段樹維護重鏈,而LCT用伸展樹(是不是很高大上),在兩棵伸展樹之間,如果它們屬於同一個LCT,那麽將有一條虛邊,連接著它們,在不影響伸展樹的正常操作前提上,保持應有的連系。
大家可以感性的認識….可以假設一開始問題給出的樹邊都是虛邊,我們人為的在上面畫重鏈,每條重鏈用一棵伸展樹維護他(就跟線段樹一個道理嘛,目標是減少暴力枚舉的時間,只不過伸展樹更加快捷靈活),關鍵的,如果沒有連邊刪邊操作,同伸展樹一樣,整棵樹的結構是不變的。
當然啦,題目也可能給出很多棵樹,我們可以臆想一下,這些樹都屬於0節點的子樹,只不過他們連的邊被“操作刪除”了,這樣也是合理的。同樣道理,當我們在解決動態樹問題的過程中,有時也會出現這棵樹被分成多份。也就是說,Link-Cut-Tree本質上這個圖可以是一個森林。

講講操作吧。
最重要的access(x):令x到當前所處的樹的根這條路徑成為偏愛路徑(相當於樹剖的重鏈),然後用splay維護,這是與樹剖最大的不同,這樣的靈活性也符合動態樹。
make_root(x):令x成為當前樹的根,但是!!不是在當前重鏈中伸展樹的根,也不是整個圖中所有點的根,而是,x當前所處的樹的根! 由於 LCT的Link和Cut操作,註定了整個圖可能出現多棵樹,樹與樹之間如果不添加邊,都是一個獨立的動態樹。
Link(x,y):讓x成為根,然後連一條虛邊到y就OK了。
Cut(x,y):先將x設為根(假設現在是點1)假設y是點6,那我們將1~6的路徑設為偏愛路徑(放在一棵伸展樹裏)將6旋轉到伸展樹的根,可以發現,點1肯定在伸展樹的最左端,讓y斷開與左端的連接就行了。
技術分享
findroot(x):同理,真正在樹中的根肯定在樹的最左邊,所以說找根其實很簡單。
PS:所以在make_root後要讓整棵伸展樹翻轉,比如說將6變為根,1,4都在它左邊,這樣就不科學了。

如果還有不明白的,可以看caioj的書,還有上caioj1439看視頻,視頻非常好!!出視頻的人改變了我的一生,從未見過有如此懂我的人,他太強了,我崇拜他一輩子!!

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
struct node
{
    int f,d,c,n,son[2],mx,ad;
    bool fz;
}tr[310000];
void add(int x)
{
    tr[x].d+=tr[x].ad;tr[x].mx+=tr[x].ad;
    int lc=tr[x].son[0],rc=tr[x].son[1];
    tr[lc].ad+=tr[x].ad;
    tr[rc].ad+=tr[x].ad;
    tr[x].ad=0;
}
void update(int x)
{
    int lc=tr[x].son[0],rc=tr[x].son[1];
    tr[x].c=tr[lc].c+tr[rc].c+tr[x].n;
    if(tr[lc].ad!=0)add(lc);
    if(tr[rc].ad!=0)add(rc);
    if(lc==0)tr[lc].mx=0;
    if(rc==0)tr[rc].mx=0;
    tr[x].mx=max(max(tr[lc].mx,tr[rc].mx),tr[x].d);
}
void reverse(int x)
{
    tr[x].fz=false;
    swap(tr[x].son[0],tr[x].son[1]);
    int lc=tr[x].son[0],rc=tr[x].son[1];
    tr[lc].fz=1-tr[lc].fz;
    tr[rc].fz=1-tr[rc].fz;
}
void rotate(int x,int w)
{
    int f=tr[x].f,ff=tr[f].f;
    int R,r;

    R=f;r=tr[x].son[w];
    tr[R].son[1-w]=r;
    if(r!=0)tr[r].f=R;

    R=ff;r=x;
         if(tr[R].son[0]==f)tr[R].son[0]=r;
    else if(tr[R].son[1]==f)tr[R].son[1]=r;
    tr[r].f=R;

    R=x;r=f;
    tr[R].son[w]=r;
    tr[r].f=R;

    update(f);
    update(x);
}
int tmp[310000];
void splay(int x,int rt)
{
    int s=0,i=x;
    while(tr[i].f!=0&&(tr[tr[i].f].son[0]==i||tr[tr[i].f].son[1]==i))
    {
        tmp[++s]=i;
        i=tr[i].f;
    } 
    tmp[++s]=i;
    while(s!=0)
    {
        i=tmp[s];s--;
        if(tr[i].fz==true)reverse(i);
        if(tr[i].ad!=0)add(i);
    }

    while(tr[x].f!=rt&&(tr[tr[x].f].son[0]==x||tr[tr[x].f].son[1]==x))//還有虛邊啊!
    {
        int f=tr[x].f,ff=tr[f].f;
        if(ff==rt||(tr[ff].son[0]!=f&&tr[ff].son[1]!=f))
        {
            if(x==tr[f].son[0])rotate(x,1);
            else               rotate(x,0);
        }
        else
        {
                 if(tr[f].son[0]==x&&tr[ff].son[0]==f){rotate(f,1);rotate(x,1);}
            else if(tr[f].son[1]==x&&tr[ff].son[0]==f){rotate(x,0);rotate(x,1);}
            else if(tr[f].son[0]==x&&tr[ff].son[1]==f){rotate(x,1);rotate(x,0);}
            else if(tr[f].son[1]==x&&tr[ff].son[1]==f){rotate(f,0);rotate(x,0);}
        }
    }
}
int n,w[310000];
void make_tree()
{
    for(int i=0;i<=n;i++)
    {
        tr[i].f=0;
        tr[i].mx=tr[i].d=w[i];
        tr[i].c=1;tr[i].n=1;
        tr[i].son[0]=tr[i].son[1]=0;
        tr[i].fz=false;tr[i].ad=0;
    }
}
void access(int x)//訪問x 
//還記得樹剖的重兒子嗎?這是令點x到整棵動態樹的根這條路徑變成偏愛路徑(相當於樹剖的重鏈),這一條路徑就是一棵伸展樹。 
{
    int y=0;
    while(x!=0)
    {
        splay(x,0);
        tr[x].son[1]=y;
        if(y!=0)tr[y].f=x;
        y=x;x=tr[x].f;
    }
}
void makeroot(int x)//讓x成為當前樹的根
{
    access(x);splay(x,0);//因為是鏈,splay之後只有左孩子(上面y=0) 
    tr[x].fz=1-tr[x].fz;//因為要讓x成為整棵樹的根,所以x的深度要最小(通過翻轉實現),為find_root做準備
}
void link(int x,int y)
{//為什麽可以直接用makeroot??因為判斷過x和y的find_root 是否相同,不相同表示x和y是不聯通的
    makeroot(x);tr[x].f=y;access(x);//刪去access是沒有影響的,但從定義上說應該加上 
}
void cut(int x,int y)
{
    makeroot(x);
    access(y);splay(y,0);
    tr[tr[y].son[0]].f=0;tr[y].son[0]=0;
    update(y);
}
int find_root(int x)//訪問完x後,x所屬的伸展樹的最左端的點就是所在樹真正的根,因為伸展樹實際意義上就是一條鏈啊!! 
{
    access(x);splay(x,0);
    while(tr[x].son[0]!=0)x=tr[x].son[0];
    return x;
}
void increase(int x,int y,int W)//令x,y處於一棵伸展樹,y為根,由於是鏈,直接更新y的ad就行了 
{
    makeroot(x);
    access(y);splay(y,0);
    tr[y].ad+=W;
}
int findmax(int x,int y)//同理,這也是一樣的 
{
    makeroot(x);
    access(y);splay(y,0);
    update(y);return tr[y].mx;
}
struct edge
{
    int x,y;
}e[310000];
int main()
{
    freopen("weight.in","r",stdin);
    freopen("weight.out","w",stdout);
    int m,op,x,y,W;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<n;i++)scanf("%d%d",&e[i].x,&e[i].y);
        for(int i=1;i<=n;i++)scanf("%d",&w[i]);
        make_tree();
        for(int i=1;i<n;i++)
            link(e[i].x,e[i].y);
        scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            scanf("%d",&op);
            if(op==1)
            {
                scanf("%d%d",&x,&y);
                if(find_root(x)==find_root(y)||x==y)
                    printf("-1\n");
                else 
                    link(x,y);
            }
            else if(op==2)
            {
                scanf("%d%d",&x,&y);
                if(find_root(x)!=find_root(y)||x==y)
                    printf("-1\n");
                else 
                    cut(x,y);
            }
            else if(op==3)
            {
                scanf("%d%d%d",&W,&x,&y);
                if(find_root(x)!=find_root(y))
                    printf("-1\n");
                else 
                    increase(x,y,W);
            }
            else 
            {
                scanf("%d%d",&x,&y);
                if(find_root(x)!=find_root(y))
                    printf("-1\n");
                else 
                    printf("%d\n",findmax(x,y));
            }
        }
        printf("\n");
    }
    return 0;
}

關於樹論【動態樹問題(LCT)】