1. 程式人生 > >樹鏈剖分正確的入門姿勢

樹鏈剖分正確的入門姿勢

  樹鏈剖分並不是一個複雜的演算法或者資料結構,只是能把一棵樹拆成鏈來處理而已,換一種說法,樹鏈剖分只是xxx資料結構/演算法在樹上的推廣,或者說,樹鏈剖分只是把樹hash到了幾段連續的區間上。(引用kuangbin大佬的)

個人對樹鏈剖分的理解:

  因為線段樹只能處理像陣列一樣的線性的資料,對於樹這樣的結構,它顯得很無力,所以我們就用“樹鏈剖分”這種東西將樹拆成一小段一小段連續的鏈,這樣我們就得到了線性的資料,但這個線性的資料使用是有限制的,比如你跨段查詢或修改的時候就會出錯,那怎麼辦呢?我們知道此時任意兩個節點之間的路徑都被分成了若干段,既然不能跨段,那我們就一段一段處理,然後再合併一下。這時你可能會有疑問:(沒有的話,假設你有0.0)這種分段做法和暴力有多大區別?這就要看你是怎樣分段的了!我們要儘量使每段更長些,那麼我們就可以將任務儘可能多的交給線段樹處理,(具體處理看程式碼),這樣其實複雜度差不多是log級別的

!

大致操作過程:

  通過兩次dfs將整顆樹分成輕鏈和重鏈(牢記同一重鏈線上段樹上是連續的),這樣做是為了將樹拆成一段一段連續的小鏈然後放到線段上去,當查詢兩個節點之間的東西時,通過top[]陣列和fa[]陣列將這兩個節點向上挪動,每次將dis[]較深的b移到fa[top[b]]處(此過程中順便處理top[b]->b線上段樹上值),重複此過程直到兩個節點的top[]相同,top[]相同就意味著此時這兩個節點在同一重鏈上,此時再重複一遍(同一重鏈線上段樹上是連續的),所以省下可以直接就用線段樹處理這兩個節點之間的值了。

技巧:

  處理以節點u為根的整個子樹:用線段樹處理[ wei[u] , wei[u] + siz[u] - 1]即可

  查詢節點u和節點v的LCA(最近公共祖先):將兩點不斷向上挪動並處理,直到兩點的top[]相同,dis[]小的點即是最近公共祖先

  處理節點u到節點v的路徑上的點:將兩點不斷向上挪動並處理,直到兩點的top[]相同,最後用線段樹處理[wei[u] , wei[v] ]即可

  處理節點u到節點v的路徑上的邊:將兩點不斷向上挪動並處理,直到兩點的top[]相同,最後用線段樹處理[wei[ son[u] ] , wei[v] ]即可(用每個點表示通向其父節點的邊)

入門:樹鏈剖分入門詳解 (講的很詳細,不懂得細心看哦)
訓練題:樹鏈剖分從入門到精通 (難度不一,適合從入門到深入哦)

下面貼個入門參照題(參照此程式碼,A了

入門題,就差不多算入門了)

Good Luck!(將程式碼註釋全部開啟即是POJ 2337的程式碼)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

struct Edge
{
    int a,b,len;
}edge[10005];
int tree[40005];
int son[10005],fa[10005],dis[10005],top[10005],siz[10005],wei[10005],len[10005],num;
vector<int>vec[10005];

void dfs1(int u,int f,int d)///樹鏈剖分套路
{
    fa[u]=f;
    dis[u]=d;
    siz[u]=1;
    son[u]=0;
    for(int i=0;i<vec[u].size();i++){
        int v=vec[u][i];
        if(v==f)continue;
        dfs1(v,u,d+1);
        siz[u]+=siz[v];
        if(siz[son[u]]<siz[v])son[u]=v;
    }
}

void dfs2(int u,int tp)///樹鏈剖分套路
{
    top[u]=tp;
    wei[u]=++num;
    if(son[u])dfs2(son[u],tp);
    for(int i=0;i<vec[u].size();i++){
        int v=vec[u][i];
        if(v==fa[u]||v==son[u])continue;
        dfs2(v,v);
    }
}

void up(int node)///線段樹更新
{
    int ll=node<<1,rr=ll|1;
    tree[node]=max(tree[ll],tree[rr]);
}

void maketree(int l,int r,int node)///建線段樹
{
    if(l==r){tree[node]=len[l];return;}
    int mid=(l+r)>>1;
    maketree(l,mid,node<<1);
    maketree(mid+1,r,node<<1|1);
    up(node);
}

void gai(int l,int r,int node,int sign,int a)///線段樹修改
{
    if(l>sign||r<sign)return;
    if(l==r){tree[node]=a;return;}
    int mid=(l+r)>>1;
    gai(l,mid,node<<1,sign,a);
    gai(mid+1,r,node<<1|1,sign,a);
    up(node);
}

int query(int l,int r,int node,int ll,int rr)///線段樹查詢
{
    if(l>rr||r<ll)return -999999999;
    if(l>=ll&&r<=rr)return tree[node];
    int mid=(l+r)>>1,ans;
    ans=query(l,mid,node<<1,ll,rr);
    ans=max(ans,query(mid+1,r,node<<1|1,ll,rr));
    return ans;
}

/*void gai2(int l,int r,int node,int ll,int rr)
{
    if(l>rr||r<ll)return;
    if(l==r){tree[node]*=-1;return;}
    int mid=(l+r)>>1;
    gai2(l,mid,node<<1,ll,rr);
    gai2(mid+1,r,node<<1|1,ll,rr);
    up(node);
}*/

int yongth(int a,int b)///樹鏈剖分路徑分段處理
{
    if(a==b)return 0;
    int ta=top[a],tb=top[b],ans=-999999999;
    while(ta!=tb){
        if(dis[ta]>dis[tb]){
            swap(ta,tb);
            swap(a,b);
        }
        ans=max(ans,query(1,num,1,wei[tb],wei[b]));
        b=fa[tb];
        tb=top[b];
    }
    if(a==b)return ans;
    if(dis[a]>dis[b])swap(a,b);
    ans=max(ans,query(1,num,1,wei[son[a]],wei[b]));
    return ans;
}

/*void Yongth(int a,int b)
{
    int ta=top[a],tb=top[b];
    while(ta!=tb){
        if(dis[ta]>dis[tb]){
            swap(ta,tb);
            swap(a,b);
        }
        gai2(1,num,1,wei[tb],wei[b]);
        b=fa[tb];
        tb=top[b];
    }
    if(a==b)return;
    if(dis[a]>dis[b])swap(a,b);
    gai2(1,num,1,wei[son[a]],wei[b]);
}*/

void init(int n)///初始化
{
    for(int i=0;i<=n;i++){
        vec[i].clear();
    }
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        int n;
        scanf(" %d",&n);
        init(n);

        int a,b;
        for(int i=1;i<n;i++){///此處待優化,待讀者深入時自會知曉,迫不及待的可以去看我的另一篇樹鏈剖分題解
            scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].len);
            vec[edge[i].a].push_back(edge[i].b);
            vec[edge[i].b].push_back(edge[i].a);
        }

        dfs1(1,1,1);
        num=0;
        dfs2(1,1);

        for(int i=1;i<n;i++){///將每條邊的權值按wei[]的順序記錄在len[]裡面,len[]即是線段樹將要處理的陣列
            if(dis[edge[i].a]<dis[edge[i].b])swap(edge[i].a,edge[i].b);
            len[wei[edge[i].a]]=edge[i].len;
        }

        maketree(1,num,1);

        char ch[30];
        while(~scanf("%s",ch)&&ch[0]!='D'){
            scanf(" %d%d",&a,&b);
            if(ch[0]=='Q')printf("%d\n",yongth(a,b));
            ///else if(ch[0]=='N')Yongth(a,b);
            else  gai(1,num,1,wei[edge[a].a],b);
        }
    }
    return 0;
}