樹鏈剖分總結

 

樹鏈剖分,是很多樹上問題很好的解決方法.                                                                                                                            比如說修改樹上路徑上點的權值.有些人說線段樹一定能解決,但是光用線段樹是不行的,這裏就可以用線段樹結合樹鏈剖分解決.       相信很多人還不知道樹鏈剖分是什麼算法,不廢話了,開始.

先來一道模板題,給定N個節點的一棵樹有K次查詢,每次查詢a和b的最近公共祖先(即LCA)   (N<=1e5,K<=1e5)                             

首先暴力肯定會被卡掉O(N*K);  可以用倍增O(NlogN+KlogN),離線tarjan O(N+K) (常數較大且在強制在線時不行)                       LCA轉RMQ利用dfs序也可以解決 O(2NlogN+K) ,今天我介紹的樹鏈剖分雖然理論時間複雜度不夠優秀,但是實際運行時間最優.     倍增216ms,離線tarjan128ms,LCA轉RMQ260ms(在K遠遠大於N時此類問題它更優),而樹剖108ms

下面我來介紹樹剖的原理,它是把一棵樹的所有邊分成兩種鏈(輕鏈和重鏈),重鏈的定義是在根節點一定的情況之下,在此節點的兒子節點中,誰下方的子樹最大,就把那一條鏈定義成重鏈,此節點連接剩下子節點的邊都定義爲輕鏈.                                                 這有什麼好處呢?請看圖.                                                                                                                                                                                  

此圖來自poj1330    剖分後就是如下情況,求LCA時遇到重鏈就一直到頂上,輕鏈就走一步.                                                                                                                             圖畫的略醜,但意思表達出來了.                                                                                                                                                 題時,可用幾個數組將圖的意思表達出來.                                                                                                                         size[]表示所有點子樹的大小. fa[]表示此節點的父親是誰.top[]表示以此節點爲起點通過重鏈能到達最上面的點,dep[]表示此節點 深度,son[]表示此節點的兒子.   在這裏我要說一下,如果一個節點有多個兒子子樹個數相同時,那麼就隨便定義一個爲重鏈就好了 .   鏈式前向星連邊,兩次dfs,第一次維護size,fa,dep,son.第二次維護top,之後遵循之前的原則,遇到重鏈跳到頂端,遇到輕鏈跳一步.暴力向上跳就行了.     代碼如下:                                                                                                                                                  

#include<stdio.h>
int dep[100001];
int size[100001];
int son[100001];
int fa[100001];
int top[100001];
int head[100001];
int to[200001];
int next[200001],idx;
bool is[100001];
void addedge(int a,int b)
{
    next[++idx]=head[a];
    head[a]=idx;
    to[idx]=b;
}
void dfs(int p)
{
    size[p]=1;
    for(int i=head[p];i;i=next[i])
    {
        if(to[i]!=fa[p])
        {
            fa[to[i]]=p;
            dep[to[i]]=dep[p]+1;
            dfs(to[i]);
            size[p]+=size[to[i]];
            if(size[to[i]]>size[son[p]])
                son[p]=to[i];
        }
    }
}
void dfs2(int p,int t)
{
    top[p]=t;
    if(son[p])
        dfs2(son[p],t);
    for(int i=head[p];i;i=next[i])
    {
        if(to[i]!=fa[p]&&to[i]!=son[p])
            dfs2(to[i],to[i]);
    }   
}
int lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]>dep[top[y]])
            x=fa[top[x]];
        else
            y=fa[top[y]];
    }
    if(dep[x]<dep[y])
        return x;
    else
        return y;
}   
int main()
{
    int Q,n,i,a,b,allfa,x,y;
    scanf("%d%d",&n,&Q);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&a,&b);
        fa[b]=a;
        is[b]=true;
        addedge(a,b);
        addedge(b,a);       
    }   
    for(int i=1;i<=n;i++)
        if(is[i]==0)
        {
            allfa=i;    
            break;      
        }
    dfs(allfa);
    dfs2(allfa,allfa);
    for(int i=1;i<=Q;i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));        
    }
}

LCA的結果就是最後x和y較淺值. 

 

樹鏈剖分還有許多應用,比如它的dfs序與線段樹的結合,再借用一下上張圖.                                                                              此序列爲: 8 4 10 16 3 12 11 2 15 7 1 14 13 5 9                       

 

                                                                                                                                                              

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章