樹鏈割分原理和實現

樹鏈割分即將樹分割成爲一段一段的線段區間,具體分割的過程就是一個dfs的過程,然後就可以將樹結構劃分爲數組結構,從而實現樹上的區間操作,即用線段樹或者樹狀數組處理。

首先就是一些必須知道的概念:

  • 重結點:子樹結點數目最多的結點;
  • 輕節點:父親節點中除了重結點以外的結點;
  • 重邊:父親結點和重結點連成的邊;
  • 輕邊:父親節點和輕節點連成的邊;
  • 重鏈:由多條重邊連接而成的路徑;
  • 輕鏈:由多條輕邊連接而成的路徑;

fa[u]:保存結點u的父親節點

dep[u]:保存結點u的深度值

siz[u]:保存以u爲根的子樹節點個數

son[u]:保存重兒子

rnk[u]:保存當前dfs標號在樹中所對應的節點

top[u]:保存當前節點所在鏈的頂端節點

tid[u]:保存樹中每個節點剖分以後的新編號(DFS的執行順序)

樹鏈剖分的兩個性質:

1,如果(u, v)是一條輕邊,那麼size(v) < size(u)/2;

2,從根結點到任意結點的路所經過的輕重鏈的個數必定都小於logn;

可以證明,樹鏈剖分的時間複雜度爲O(nlog^2n)

 

點權的樹鏈剖分

兩次dfs用於獲取帶最長鏈的樹鏈:

vector<int> tr[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn];   //tree編號轉dfs編號
int rnk[maxn];   //dfs編號轉tree編號
int dfsnum;

void dfs1(int u,int father,int depth)    //當前節點、父節點、層次深度
{
    fa[u]=father;
    dep[u]=depth;
    sz[u]=1;    //這個點本身size=1
    int len=tr[u].size();
    for(int i=0;i<len;i++)
    {
        int to=tr[u][i];
        if(to==father)
            continue;
        dfs1(to,u,depth+1);    //層次深度+1
        sz[u]+=sz[to];    //子節點的size已被處理,用它來更新父節點的size
        if(sz[to]>sz[son[u]])
            son[u]=to;    //選取size最大的作爲重兒子
    }
}

void dfs2(int u,int t)    //當前節點、重鏈頂端
{
    top[u]=t;
    tid[u]=++dfsnum;    //標記dfs序
    rnk[dfsnum]=u;    //序號cnt對應節點u
    if(!son[u])
        return;
    dfs2(son[u],t);
    /*我們選擇優先進入重兒子來保證一條重鏈上各個節點dfs序連續,
    一個點和它的重兒子處於同一條重鏈,所以重兒子所在重鏈的頂端還是t*/
    int len=tr[u].size();
    for(int i=0;i<len;i++)
    {
        int to=tr[u][i];
        if(to!=son[u] && to!=fa[u])
            dfs2(to,to);    //一個點位於輕鏈底端,那麼它的top必然是它本身
    }
}

void treeSplit(int root)
{
    memset(son,0,sizeof(son));
    memset(sz,0,sizeof(sz));
    dfsnum=0;
    dfs1(root,-1,0);
    dfs2(root,root);
}

由於對於點的存儲vector只需要存儲int,因此對於邊的優化影響不大

邊存儲優化(完整的最優化樹鏈剖分 點權)


struct edge
{
    int to;
    int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void initTree()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
    e[tot].to=v;
    e[tot].next=head[u];
    head[u]=tot;
    tot++;
}
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn];   //tree編號轉dfs編號
int rnk[maxn];   //dfs編號轉tree編號
int dfsnum;

void dfs1(int u,int father,int depth)    //當前節點、父節點、層次深度
{
    fa[u]=father;
    dep[u]=depth;
    sz[u]=1;    //這個點本身size=1
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int to=e[i].to;
        if(to==father)
            continue;
        dfs1(to,u,depth+1);    //層次深度+1
        sz[u]+=sz[to];    //子節點的size已被處理,用它來更新父節點的size
        if(sz[to]>sz[son[u]])
            son[u]=to;    //選取size最大的作爲重兒子
    }
}

void dfs2(int u,int t)    //當前節點、重鏈頂端
{
    top[u]=t;
    tid[u]=++dfsnum;    //標記dfs序
    rnk[dfsnum]=u;    //序號cnt對應節點u
    if(!son[u])
        return;
    dfs2(son[u],t);
    /*我們選擇優先進入重兒子來保證一條重鏈上各個節點dfs序連續,
    一個點和它的重兒子處於同一條重鏈,所以重兒子所在重鏈的頂端還是t*/
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int to=e[i].to;
        if(to!=son[u] && to!=fa[u])
            dfs2(to,to);    //一個點位於輕鏈底端,那麼它的top必然是它本身
    }
}
void treeSplit(int root)
{
    memset(son,0,sizeof(son));
    memset(sz,0,sizeof(sz));
    dfsnum=0;
    dfs1(root,-1,0);
    dfs2(root,root);
}

如果不用需要最長鏈,可以修改成一次dfs(該方法會加長後續操作時間)

vector<int> tree[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int son[maxn];
int tid[maxn];   //tree編號轉dfs編號
int rnk[maxn];   //dfs編號轉tree編號
int dfsnum=0;
void init()
{
    dfsnum=0;
    memset(son,-1,sizeof(son));
}

//以上數組,並不是全部都需要,按照題目要求,可以用上述數組化簡操作的時候才進行記錄
void dfs(int u,int t,int father,int depth)
{
    //u: 當前結點  t:該鏈的top結點 father: 父親結點   depth: 深度
    top[u]=t;
    fa[u]=father;
    dep[u]=depth;

    tid[u]=++dfsnum;
    rnk[tid[u]]=u;

    bool first=true;
    for(int i=0;i<tree[u].size();i++)
    {
        int to=tree[u][i];
        if(to!=fa[u])
        {
            if(first)
            {
                son[u]=to;
                dfs(to,t,u,depth + 1);
                first=false;
            }
            else
                dfs(to,to,u,depth + 1);
        }
    }
}

int main()
{
    dfs(root,root,-1,1);
}

邊權的樹鏈剖分

int num[maxn];
struct edge
{
    int to;
    int w;
    int num;
    edge(){}
    edge(int a,int b,int c){to=a;w=b;num=c;}
};
vector<edge> tr[maxn];
int eMapDfs[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn];   //tree編號轉dfs編號
int rnk[maxn];   //dfs編號轉tree編號
int dfsnum;

void dfs1(int u,int father,int depth)    //當前節點、父節點、層次深度
{
    fa[u]=father;
    dep[u]=depth;
    sz[u]=1;    //這個點本身size=1
    int len=tr[u].size();
    for(int i=0;i<len;i++)
    {
        int to=tr[u][i].to;
        if(to==father)
            continue;
        num[to]=tr[u][i].w;    //承認to是u的兒子了,將邊權下放
        eMapDfs[tr[u][i].num]=to;   //當前邊的編號對應的tree樹的編號
        dfs1(to,u,depth+1);    //層次深度+1
        sz[u]+=sz[to];    //子節點的size已被處理,用它來更新父節點的size
        if(sz[to]>sz[son[u]])
            son[u]=to;    //選取size最大的作爲重兒子
    }
}

void dfs2(int u,int t)    //當前節點、重鏈頂端
{
    top[u]=t;
    tid[u]=++dfsnum;    //標記dfs序
    rnk[dfsnum]=u;    //序號cnt對應節點u
    if(!son[u])
        return;
    dfs2(son[u],t);
    /*我們選擇優先進入重兒子來保證一條重鏈上各個節點dfs序連續,
    一個點和它的重兒子處於同一條重鏈,所以重兒子所在重鏈的頂端還是t*/
    int len=tr[u].size();
    for(int i=0;i<len;i++)
    {
        int to=tr[u][i].to;
        if(to!=son[u] && to!=fa[u])
            dfs2(to,to);    //一個點位於輕鏈底端,那麼它的top必然是它本身
    }
}

void treeSplit(int root)
{
    memset(num,0,sizeof(num));
    memset(son,0,sizeof(son));
    memset(sz,0,sizeof(sz));
    dfsnum=0;
    dfs1(root,-1,0);
    dfs2(root,root);
}

邊vector的存儲優化

(完整的最優化樹鏈剖分 邊權)

struct edge
{
    int id;
    int to;
    int w;
    int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void initTree()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w,int id)
{
    e[tot].to=v;
    e[tot].next=head[u];
    e[tot].id=id;
    e[tot].w=w;
    head[u]=tot;
    tot++;
}
int num[maxn];
int eMapDfs[maxn];     //邊序號轉該邊對應的權值下放的樹節點編號
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];          //用於處理樹鏈剖分的長鏈短鏈
int son[maxn];
int tid[maxn];   //tree編號轉dfs編號
int rnk[maxn];   //dfs編號轉tree編號
int dfsnum;
void dfs1(int u,int father,int depth)    //當前節點、父節點、層次深度
{
    fa[u]=father;
    dep[u]=depth;
    sz[u]=1;    //這個點本身size=1
    for(int i=head[u];i!=-1;i=e[i].next)
    {       //當前邊爲e[i]
        int to=e[i].to;
        if(to==father)
            continue;
        num[to]=e[i].w;
        eMapDfs[e[i].id]=to;
        dfs1(to,u,depth+1);    //層次深度+1
        sz[u]+=sz[to];    //子節點的size已被處理,用它來更新父節點的size
        if(sz[to]>sz[son[u]])
            son[u]=to;    //選取size最大的作爲重兒子
    }
}
void dfs2(int u,int t)    //當前節點、重鏈頂端
{
    top[u]=t;
    tid[u]=++dfsnum;    //標記dfs序
    rnk[dfsnum]=u;    //序號cnt對應節點u
    if(!son[u])
        return;
    dfs2(son[u],t);
    /*我們選擇優先進入重兒子來保證一條重鏈上各個節點dfs序連續,
    一個點和它的重兒子處於同一條重鏈,所以重兒子所在重鏈的頂端還是t*/
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int to=e[i].to;
        if(to!=son[u] && to!=fa[u])
            dfs2(to,to);    //一個點位於輕鏈底端,那麼它的top必然是它本身
    }
}

void treeSplit(int root)
{
    memset(num,0,sizeof(num));
    memset(son,0,sizeof(son));
    memset(sz,0,sizeof(sz));
    dfsnum=0;
    dfs1(root,-1,0);
    dfs2(root,root);
}

當樹鏈構造成功之後,我們就可以利用線段樹來維護樹上的信息,來處理某點子樹和到根路徑的信息。

 

通過dfs我們已經保證一條重鏈上各個節點dfs序連續,那麼可以想到,我們可以通過數據結構(以線段樹爲例)來維護一條重鏈的信息
回顧上文的那個題目,修改和查詢操作原理是類似的,以查詢操作爲例,其實就是個LCA,不過這裏使用了top來進行加速,因爲top可以直接跳轉到該重鏈的起始結點,輕鏈沒有起始結點之說,他們的top就是自己。需要注意的是,每次循環只能跳一次,並且讓結點深的那個來跳到top的位置,避免兩個一起跳從而插肩而過。

 

樹鏈構建線段樹:

❤注意:由於線段樹是跟隨dfs序號構建的,因此需要用tid將樹編號轉化成dfs編號(下例)❤

ll sum[maxn<<2];   //隨其他修改
void build(int l,int r,int pos)   //k代表了掛該數據存儲位置在第幾個數組中,l,r代表該位置數組覆蓋範圍
{
    if(l==r)
    {
        sum[pos]=num[rnk[l]];     //dfs編號轉換成tree編號,在轉換成樹上val值
        return;
    }
    int mid=(l+r)/2;

    build(l,mid,pos<<1);
    build(mid+1,r,pos<<1|1);
    sum[pos]=sum[pos<<1]+sum[pos<<1|1];
}

當線段樹構建完成後,就可以在線段樹上進行一系列的修改與查詢過程了。具體的修改與查詢與具體的操作有關

圖與樹的dfs:https://blog.csdn.net/qq_38890926/article/details/81222698

 

樹鏈路徑

求取結點u到v路徑上鍊的值(值在點上)

ll getTreeLine(int u,int v)   //傳入樹編號
{
    ll ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);

        ans=ans+query(1,n,1,tid[top[u]],tid[u]);
        u=fa[top[u]];
    }

    if(dep[u]>dep[v]) swap(u,v);
    ans=ans+query(1,n,1,tid[u],tid[v]);

    return ans;
}

求取結點u到v路徑上鍊的值(值在邊上)

ll getTreeLine(int u,int v)   //傳入樹編號
{
    ll ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);

        ans=ans+query(1,n,1,tid[top[u]],tid[u]);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    ans=ans+query(1,n,1,tid[u]+1,tid[v]);

    return ans;
}

 

 

點權,單點修改區間查詢

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cstring>
#include<sstream>
#include<iomanip>
#include<fstream>
#include<fstream>

#define maxn 100005
#define maxm 105
typedef long long ll;
const int inf=1e8+7;
const ll mod=1000000007;
const double eps=1e-10;
using namespace std;


inline void read(int &x){scanf("%d",&x);}
inline void readll(ll &x){scanf("%lld",&x);}

int n;
struct edge
{
    int to;
    int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void init()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
    e[tot].to=v;
    e[tot].next=head[u];
    head[u]=tot;
    tot++;
}
int a[maxn];
struct TreeSplit
{
    int tp[maxn],fa[maxn],d[maxn],son[maxn];//關心鏈上信息
    int tid[maxn],rnk[maxn];//tr轉dfs,dfs轉tr
    int dfn,sz[maxn];
    void dfs1(int u,int f,int dep)
    {
        fa[u]=f;d[u]=dep;sz[u]=1;son[u]=0;
        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int to=e[i].to;if(to==f)continue;
            dfs1(to,u,dep+1);
            sz[u]=sz[u]+sz[to];
            if(sz[to]>sz[son[u]])son[u]=to;//重兒子size最大
        }
    }
    void dfs2(int u,int t)//當前點,重鏈頂
    {
        tp[u]=t;tid[u]=++dfn;rnk[dfn]=u;
        if(!son[u])return;
        dfs2(son[u],t);//先重兒子保證重鏈dfs序連續
        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int to=e[i].to;
            if(to!=son[u]&&to!=fa[u])dfs2(to,to);//點在輕鏈底端,top是它自己
        }
    }
    void treeSplit(int rt)
    {
        dfn=0;
        dfs1(rt,-1,0);dfs2(rt,rt);
    }
    ll getTreeLinemax(int u,int v);
    ll getTreeLinesum(int u,int v);
}tree;
struct node
{
    int l,r;
    ll sum;
    ll maxnum;
};
struct SegmentTree //單點修改,區間查詢
{
    node tr[maxn<<2];
    inline void buildInit(int p)
    {
        tr[p].maxnum=tr[p].sum=a[tree.rnk[tr[p].l]];
    }
    inline void push_up(int p)
    {
        tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;
        tr[p].maxnum=max(tr[p<<1].maxnum,tr[p<<1|1].maxnum);
    }
    inline void changeOp(int p,ll v)
    {
        tr[p].maxnum=tr[p].sum=v;
    }
    inline node queryUnion(node a,node b)
    {
        node tmp;
        tmp.sum=a.sum+b.sum;
        tmp.maxnum=max(a.maxnum,b.maxnum);
        return tmp;
    }
    void build(int l,int r,int p)
    {
        tr[p].l=l,tr[p].r=r;
        if(l==r){buildInit(p);return;}
        int mid=(l+r)>>1;
        build(l,mid,p<<1);build(mid+1,r,p<<1|1);
        push_up(p);
    }
    void change(int p,int t,ll v)
    {
        if(tr[p].l==tr[p].r){changeOp(p,v);return;}
        int mid=(tr[p].l+tr[p].r)>>1;
        if(t<=mid) change(p<<1,t,v);
        else change(p<<1|1,t,v);
        push_up(p);
    }
    node query(int p,int lt,int rt)
    {
        if(tr[p].l>=lt && tr[p].r<=rt){return tr[p];}
        int mid=(tr[p].l+tr[p].r)>>1;
        if(rt<=mid) return query(p<<1,lt,rt);
        if(lt>mid) return query(p<<1|1,lt,rt);
        return queryUnion(query(p<<1,lt,rt),query(p<<1|1,lt,rt));
    }
}st;

ll TreeSplit::getTreeLinemax(int u,int v)   //傳入樹編號
{
    ll ans=-inf;
    while(tp[u]!=tp[v])
    {
        if(d[tp[u]]<d[tp[v]])swap(u,v);
        ans=max(ans,st.query(1,tid[tp[u]],tid[u]).maxnum);
        u=fa[tp[u]];
    }
    if(d[u]<d[v])swap(u,v);
    ans=max(ans,st.query(1,tid[v],tid[u]).maxnum);
    return ans;
}
ll TreeSplit::getTreeLinesum(int u,int v)   //傳入樹編號
{
    ll ans=0;
    while(tp[u]!=tp[v])
    {
        if(d[tp[u]]<d[tp[v]])swap(u,v);
        ans=ans+st.query(1,tid[tp[u]],tid[u]).sum;
        u=fa[tp[u]];
    }
    if(d[u]<d[v])swap(u,v);
    ans=ans+st.query(1,tid[v],tid[u]).sum;
    return ans;
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        init();
        for(int i=1;i<n;i++)
        {
            int u,v;scanf("%d %d",&u,&v);
            addedge(u,v);addedge(v,u);
        }
        tree.treeSplit(1);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        st.build(1,n,1);
        int m;scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            char op[10];int x,y;scanf("%s %d %d",op,&x,&y);
            if(op[3]=='X')printf("%lld\n",tree.getTreeLinemax(x,y));
            if(op[3]=='M')printf("%lld\n",tree.getTreeLinesum(x,y));
            if(op[3]=='N')st.change(1,tree.tid[x],y);
        }
    }
    return 0;
}

邊權,區間查詢

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cstring>
#include<sstream>
#include<iomanip>
#include<fstream>
#include<fstream>

#define maxn 100005
#define maxm 105
typedef long long ll;
const int inf=1e9+7;
const ll mod=1000000007;
const double eps=1e-10;
using namespace std;


inline void read(int &x){scanf("%d",&x);}
inline void readll(ll &x){scanf("%lld",&x);}

int n,q;
struct edge
{
    int to;
    int w;
    int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void init()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)
{
    e[tot].to=v;
    e[tot].w=w;
    e[tot].next=head[u];
    head[u]=tot;
    tot++;
}
int a[maxn];
struct TreeSplit
{
    int tp[maxn],fa[maxn],d[maxn],son[maxn];//關心鏈上信息
    int tid[maxn],rnk[maxn];//tr轉dfs,dfs轉tr
    int dfn,sz[maxn];
    void dfs1(int u,int f,int dep)
    {
        fa[u]=f;d[u]=dep;sz[u]=1;son[u]=0;
        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int to=e[i].to;if(to==f)continue;
            a[to]=e[i].w;
            dfs1(to,u,dep+1);
            sz[u]=sz[u]+sz[to];
            if(sz[to]>sz[son[u]])son[u]=to;//重兒子size最大
        }
    }
    void dfs2(int u,int t)//當前點,重鏈頂
    {
        tp[u]=t;tid[u]=++dfn;rnk[dfn]=u;
        if(!son[u])return;
        dfs2(son[u],t);//先重兒子保證重鏈dfs序連續
        for(int i=head[u];i!=-1;i=e[i].next)
        {
            int to=e[i].to;
            if(to!=son[u]&&to!=fa[u])dfs2(to,to);//點在輕鏈底端,top是它自己
        }
    }
    void treeSplit(int rt)
    {
        dfn=0;
        dfs1(rt,-1,0);dfs2(rt,rt);
    }
    ll getTreeLinemin(int u,int v);
}tree;

struct node
{
    int l,r;
    ll minnum;
    node(){}
    node(ll x){minnum=x;}
};
struct SegmentTree //單點修改,區間查詢
{
    node tr[maxn<<2];
    inline void buildInit(int p)
    {
        tr[p].minnum=a[tree.rnk[tr[p].l]];
    }
    inline void push_up(int p)
    {
        tr[p].minnum=min(tr[p<<1].minnum,tr[p<<1|1].minnum);
    }
    inline node queryUnion(node a,node b)
    {
        node tmp;
        tmp.minnum=min(a.minnum,b.minnum);
        return tmp;
    }
    void build(int l,int r,int p)
    {
        tr[p].l=l,tr[p].r=r;
        if(l==r){buildInit(p);return;}
        int mid=(l+r)>>1;
        build(l,mid,p<<1);build(mid+1,r,p<<1|1);
        push_up(p);
    }
    node query(int p,int lt,int rt)
    {
        if(lt>rt)return node(inf);
        if(tr[p].l>=lt && tr[p].r<=rt){return tr[p];}
        int mid=(tr[p].l+tr[p].r)>>1;
        if(rt<=mid) return query(p<<1,lt,rt);
        if(lt>mid) return query(p<<1|1,lt,rt);
        return queryUnion(query(p<<1,lt,rt),query(p<<1|1,lt,rt));
    }
}st;


ll TreeSplit::getTreeLinemin(int u,int v)   //傳入樹編號
{
    ll ans=inf;
    while(tp[u]!=tp[v])
    {
        if(d[tp[u]]<d[tp[v]])swap(u,v);
        ans=min(ans,st.query(1,tid[tp[u]],tid[u]).minnum);
        u=fa[tp[u]];
    }
    if(d[u]<d[v])swap(u,v);
    ans=min(ans,st.query(1,tid[v]+1,tid[u]).minnum);
    return ans;
}

int main()
{
    cin>>n>>q;
    init();
    for(int i=1;i<n;i++)
    {
        int f,t,w;cin>>f>>t>>w;
        addedge(f,t,w);addedge(t,f,w);
    }
    memset(a,0,sizeof(a));
    tree.treeSplit(1);
    st.build(1,n,1);
    while(q--)
    {
        int f,t;cin>>f>>t;
        cout<<tree.getTreeLinemin(f,t)<<endl;
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章