樹鏈剖分詳解

樹鏈剖分是個很簡單的算法

樹鏈剖分一共分爲兩種,一種是重鏈剖分,比較常見;還有一種是長鏈剖分,比較少見

一.重鏈剖分

如下講解都以Luogu P3384 【模板】樹鏈剖分爲例

重兒子:對於每個非葉子節點,它的兒子中 以那個兒子爲根的子樹節點數最大的兒子 爲該節點的重兒子 (Ps: 感謝@shzr大佬指出我此句話的表達不嚴謹qwq, 已修改)

輕兒子:對於每個非葉子節點,它的兒子中 非重兒子 的剩下全部兒子即爲輕兒子

葉子節點沒有重兒子也沒有輕兒子(由於它沒有兒子。。)

重邊:一個父親鏈接他的重兒子的邊稱爲重邊 //原寫法:鏈接任意兩個重兒子的邊叫作重邊

輕邊:剩下的即爲輕邊

重鏈:相鄰重邊連起來的 鏈接一條重兒子 的鏈叫重鏈

對於葉子節點,若其爲輕兒子,則有一條以本身爲起點的長度爲1的鏈

每一條重鏈以輕兒子爲起點

1256986-20171203120143991-1630008815.png

這圖好像是洛咕上的,我仍是懶得本身畫

提及來這些概念實際很簡單

但寫起來仍是要有較強碼力的

咱們先要寫把輕重鏈求出的函數

一共須要寫兩個函數

1.dfs1

dfs1主要求出:

1.該節點的子樹大小(1+全部子節點子樹大小之和)

2.重兒子(找到全部子節點中子樹大小最大的)

3.父節點

4.深度

dfs1仍是比較簡單的qaq

inline void dfs1(register int x)
{
    size[x]=1;
    for(register int i=head[x];i;i=e[i].next)
        if(e[i].to!=fa[x])
        {
            dep[e[i].to]=dep[x]+1;
            fa[e[i].to]=x;
            dfs1(e[i].to);
            size[x]+=size[e[i].to];
            if(size[e[i].to]>size[son[x]])
                son[x]=e[i].to;
        }
}

dfs2

dfs2是重鏈剖分的重點

dfs2要求出:

1.樹的dfs序(優先搜重兒子)

2.在樹的dfs序之下,珂以把樹上的值存到連續的數列中,到時就珂以線段樹維護

3.每一個重鏈的頂端,方便到時候跳鏈(不懂的話後面會講)

inline void dfs2(register int x,register int t)
{
    dl[x]=++tot;
    a[tot]=ch[x];
    top[x]=t;
    if(son[x])
        dfs2(son[x],t);
    for(register int i=head[x];i;i=e[i].next)
        if(e[i].to!=fa[x]&&e[i].to!=son[x])
            dfs2(e[i].to,e[i].to);
}

跑完兩個dfs以後就珂以用線段樹

build建樹:

inline void pushup(register int x)
{
    sum[x]=sum[x<<1]+sum[x<<1|1];
    sum[x]%=mod;
}
inline void build(register int x,register int l,register int r)
{
    if(l==r)
    {
        sum[x]=a[l];
        tag[x]=0;
        return;
    }
    int mid=l+r>>1;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
    pushup(x);
}

下面是處理查詢

操做1:把x節點到y節點路徑上的值加z

這裏須要一個跳鏈的函數——cal1

inline void pushdown(register int x,register int l,register int r)
{
    int ls=x<<1,rs=x<<1|1,mid=l+r>>1;
    sum[ls]+=(mid-l+1)*tag[x];
    sum[rs]+=(r-mid)*tag[x];
    tag[ls]+=tag[x];
    tag[rs]+=tag[x];
    sum[ls]%=mod;
    sum[rs]%=mod;
    tag[ls]%=mod;
    tag[rs]%=mod;
    tag[x]=0;
}
inline void update(register int x,register int l,register int r,register int L,register int R,register int k)
{
    if(L<=l&&r<=R)
    {
        sum[x]+=(r-l+1)*k;
        tag[x]+=k;
        sum[x]%=mod;
        tag[x]%=mod;
        return;
    }
    if(tag[x])
        pushdown(x,l,r);
    int mid=l+r>>1;
    if(L<=mid)
        update(x<<1,l,mid,L,R,k);
    if(R>=mid+1)
        update(x<<1|1,mid+1,r,L,R,k);
    pushup(x);
}
inline void cal1(register int x,register int y,register int z)
{
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(dep[fx]<dep[fy])
        {
            swap(x,y);
            swap(fx,fy);
        }
        update(1,1,tot,dl[fx],dl[x],z);
        x=fa[fx];
        fx=top[x];
    }
    if(dl[x]>dl[y])
        swap(x,y);
    update(1,1,tot,dl[x],dl[y],z);
}

操做2:查詢x到y路徑點權之和

和操做1差很少,須要跳鏈

inline ll query(register int x,register int l,register int r,register int L,register int R)
{
    if(L<=l&&r<=R)
        return sum[x];
    if(tag[x])
        pushdown(x,l,r);
    ll res=0;
    int mid=l+r>>1;
    if(L<=mid)
        res+=query(x<<1,l,mid,L,R)%mod;
    if(R>=mid+1)
        res+=query(x<<1|1,mid+1,r,L,R)%mod;
    return res%mod;
}
inline ll cal2(register int x,register int y)
{
    ll res=0;
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(dep[fx]<dep[fy])
        {
            swap(x,y);
            swap(fx,fy);
        }
        res=(res%mod+query(1,1,tot,dl[fx],dl[x])%mod)%mod;
        x=fa[fx];
        fx=top[x];
    }
    if(dl[x]>dl[y])
        swap(x,y);
    res=(res%mod+query(1,1,tot,dl[x],dl[y])%mod)%mod;
    return res%mod;
}

操做3:把x的子樹內全部節點全值加z

考慮到子樹內dfs序是相連的

因此被修改區間是一個連續的區間,因此直接上線段樹

update(1,1,tot,dl[x],dl[x]+size[x]-1,z%mod);

操做四:求x的子樹內全部節點的和

和操做3同樣,珂以直接用線段樹

write(query(1,1,tot,dl[x],dl[x]+size[x]-1)%mod);

最後上一下重鏈剖分總體代碼

#include <bits/stdc++.h>
#define ll long long
#define N 100005
using namespace std;
inline ll read()
{
    register ll x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*f;
}
inline void write(register ll x)
{
    if(!x)putchar('0');if(x<0)x=-x,putchar('-');
    static int sta[36];int tot=0;
    while(x)sta[tot++]=x%10,x/=10;
    while(tot)putchar(sta[--tot]+48);
}
struct node{
    int to,next;
}e[N<<1];
int head[N],cnt=0;
inline void add(register int u,register int v)
{
    e[++cnt]=(node){v,head[u]};
    head[u]=cnt;
}
ll ch[N];
ll n,m,rt,mod;
ll size[N],dep[N],fa[N],son[N];
ll tot=0,dl[N],a[N],top[N];
inline void dfs1(register int x)
{
    size[x]=1;
    for(register int i=head[x];i;i=e[i].next)
        if(e[i].to!=fa[x])
        {
            dep[e[i].to]=dep[x]+1;
            fa[e[i].to]=x;
            dfs1(e[i].to);
            size[x]+=size[e[i].to];
            if(size[e[i].to]>size[son[x]])
                son[x]=e[i].to;
        }
}
inline void dfs2(register int x,register int t)
{
    dl[x]=++tot;
    a[tot]=ch[x];
    top[x]=t;
    if(son[x])
        dfs2(son[x],t);
    for(register int i=head[x];i;i=e[i].next)
        if(e[i].to!=fa[x]&&e[i].to!=son[x])
            dfs2(e[i].to,e[i].to);
}
ll sum[N<<3],tag[N<<3];
inline void pushup(register int x)
{
    sum[x]=sum[x<<1]+sum[x<<1|1];
    sum[x]%=mod;
}
inline void build(register int x,register int l,register int r)
{
    if(l==r)
    {
        sum[x]=a[l];
        tag[x]=0;
        return;
    }
    int mid=l+r>>1;
    build(x<<1,l,mid);
    build(x<<1|1,mid+1,r);
    pushup(x);
}
inline void pushdown(register int x,register int l,register int r)
{
    int ls=x<<1,rs=x<<1|1,mid=l+r>>1;
    sum[ls]+=(mid-l+1)*tag[x];
    sum[rs]+=(r-mid)*tag[x];
    tag[ls]+=tag[x];
    tag[rs]+=tag[x];
    sum[ls]%=mod;
    sum[rs]%=mod;
    tag[ls]%=mod;
    tag[rs]%=mod;
    tag[x]=0;
}
inline void update(register int x,register int l,register int r,register int L,register int R,register int k)
{
    if(L<=l&&r<=R)
    {
        sum[x]+=(r-l+1)*k;
        tag[x]+=k;
        sum[x]%=mod;
        tag[x]%=mod;
        return;
    }
    if(tag[x])
        pushdown(x,l,r);
    int mid=l+r>>1;
    if(L<=mid)
        update(x<<1,l,mid,L,R,k);
    if(R>=mid+1)
        update(x<<1|1,mid+1,r,L,R,k);
    pushup(x);
}
inline ll query(register int x,register int l,register int r,register int L,register int R)
{
    if(L<=l&&r<=R)
        return sum[x];
    if(tag[x])
        pushdown(x,l,r);
    ll res=0;
    int mid=l+r>>1;
    if(L<=mid)
        res+=query(x<<1,l,mid,L,R)%mod;
    if(R>=mid+1)
        res+=query(x<<1|1,mid+1,r,L,R)%mod;
    return res%mod;
}
inline void cal1(register int x,register int y,register int z)
{
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(dep[fx]<dep[fy])
        {
            swap(x,y);
            swap(fx,fy);
        }
        update(1,1,tot,dl[fx],dl[x],z);
        x=fa[fx];
        fx=top[x];
    }
    if(dl[x]>dl[y])
        swap(x,y);
    update(1,1,tot,dl[x],dl[y],z);
}
inline ll cal2(register int x,register int y)
{
    ll res=0;
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(dep[fx]<dep[fy])
        {
            swap(x,y);
            swap(fx,fy);
        }
        res=(res%mod+query(1,1,tot,dl[fx],dl[x])%mod)%mod;
        x=fa[fx];
        fx=top[x];
    }
    if(dl[x]>dl[y])
        swap(x,y);
    res=(res%mod+query(1,1,tot,dl[x],dl[y])%mod)%mod;
    return res%mod;
}
int main()
{
    n=read(),m=read(),rt=read(),mod=read(); 
    for(register int i=1;i<=n;++i)
        ch[i]=read(),ch[i]%=mod;
    for(register int i=1;i<n;++i)
    {
        int u=read(),v=read();
        add(u,v),add(v,u);
    }
    dep[rt]=1;
    fa[rt]=rt;
    dfs1(rt);
    dfs2(rt,rt);
    build(1,1,n);
    while(m--)
    {
        int opt=read();
        if(opt==1)
        {
            int x=read(),y=read(),z=read();
            cal1(x,y,z%mod);
        }
        else if(opt==2)
        {
            int x=read(),y=read();
            write(cal2(x,y)%mod);
            printf("\n");
        }
        else if(opt==3)
        {
            int x=read(),z=read();
            update(1,1,tot,dl[x],dl[x]+size[x]-1,z%mod);
        }
        else
        {
            int x=read();
            write(query(1,1,tot,dl[x],dl[x]+size[x]-1)%mod);
            printf("\n");
        }
    }
    return 0;
}

相關題目

1.Luogu P2146 [NOI2015]軟件包管理器

樹剖練手好題

2.Luogu CF343D Water Tree

樹剖後用珂朵莉樹

3.Luogu CF375D Tree and Queries

樹剖後莫隊暴力求解(也能夠稱之爲樹的dfs序)

4.Luogu P4069 [SDOI2016]遊戲

樹剖+李超線段樹

長鏈剖分

咕咕咕

相關文章
相關標籤/搜索