P3384 【模板】樹鏈剖分 題解&&樹鏈剖分詳解

題外話:ios

一道至今爲止作題時間最長的題:編程

begin at 8.30A.M函數

 

而後求助_yjk dalao後ui

 

 

 

 

 

 

 

 最後一次搞取模:spa

 

 

 awsl指針

正解開始:code

題目連接blog

樹鏈剖分,指的是將一棵樹經過兩次遍歷後將一棵樹分紅重鏈,輕邊的過程。get

咱們定義:string

重兒子:每一個點的子樹中,子樹大小(即節點數)最大的子節點

輕兒子:除重兒子外的其餘子節點

重邊:每一個節點與其重兒子間的邊

輕邊:每一個節點與其輕兒子間的邊

重鏈:重邊連成的鏈

輕鏈:輕邊連成的鏈(目前沒用到過,仍是太菜

因而乎,咱們來舉個栗子:

 

 其中,紅色邊爲重邊,天然,它們組成的鏈就是重鏈嘍。你能夠按照定義從根節點往下人腦dfs(???)一遍模擬過程,方便理解dfs代碼。

窩們發現:重鏈和輕邊交替出現(沒什麼用)。

而後,理解完上面的部分呢,咱們先安排一下兩個dfs,用來剖分整棵樹以及dfs過程當中順便搞一下其餘東西(面向題目編程)。。

第一個dfs:

做用:求出每個節點的深度dep,定義father指針fa[]指向本身的父親節點,求出子樹的大小size並順便找到重兒子son

代碼以下:

inline void dfs1(int now,int f,int deep)
{
    dep[now]=deep;//now指的是當前節點
    fa[now]=f;//指向父親節點
    size[now]=1;//加上本身
    int maxn=-1,maxson=0;//初始
    for(int i=hea[now];i;i=edge[i].next)//鏈式存圖
    {
        int v=edge[i].to;
        if(v==f)continue;//不要再找回去了。。
        dfs1(v,now,deep+1);//先把本身子樹的信息肯定好
        size[now]+=size[v];//這時候size[v]已經肯定了,咱們就往上統計大小
        if(size[v]>maxn)maxson=v,maxn=size[v];//根據定義,求出子樹最大的兒子重兒子
    }
    son[now]=maxson;//for完了就找最大的賦值
}

到這裏,咱們成功地把重兒子都給找了出來,下一步差連成鏈了。

第二遍dfs:

在第一遍dfs已經找到了重兒子的基礎上,咱們將每一條重鏈上的節點的編號弄成相鄰的,也就是一條重鏈上節點編號相鄰,這樣保證了一條重鏈能夠被劃分爲一個區間,方便之後的跳躍和區間查詢操做。

天然地,咱們須要在遍歷時先遍歷重兒子來保證區間順序相連。

代碼以下:

inline void dfs2(int now,int ttop)//top表明一個重鏈的頭,也就是dfs序最小的那個
{
    dfn[now]=++num2;//num1在鏈式前向星中用到了。233
    a0[num2]=a[now];//新的順序,來使一條重鏈上節點編號相鄰
    top[now]=ttop;//記指針指向一條重鏈中的鏈頭。
    if(!son[now])return;//若是沒兒子,直接返回了
    dfs2(son[now],ttop);//先搜重兒子
    for(int i=hea[now];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa[now]||v==son[now])continue;//father和son已經搜過了
        dfs2(v,v);//以當前節點爲重鏈頭部搜下去,而再也不是ttop
    }
}

 

樹鏈剖分主體部分完結。。。(大霧)

 由於最重要的部分不是剖分而是與其餘知識結合。。(霾)

最重要的部分也是我一直不懂的部分):

關於詢問樹上兩點路徑權值之和以及修改權值之和的問題:

天然,咱們要把路徑檢索出來。。

衆所周知,對於一棵樹,任意兩個點間,有且僅有一條路徑,且這條路徑通過他們的LCA。這說明,在查詢和修改兩點路徑時,咱們能夠將樹鏈剖分求lca與線段樹區間修改有機結合

怎麼結合呢:

注意一下描述:

對於咱們要求的兩個點的lca,容易想到加速的方法爲:順着一條樹鏈往上跳,直接跳到樹鏈頭部,這樣一次可以跨越整條樹鏈。具體的方法:

1.找到深度更大的那個節點。

2.若是兩個節點所在重鏈的頭部不爲同一個節點的話,說明他們的lca比他們的樹鏈頭部的深度還要淺,那麼咱們就能夠將深度較深的節點跳到他的上一個重鏈的尾部(也就是x=fa[top[x]])來繼續查找,這樣咱們就能夠跳過一整條重鏈了。

(2的話呢,使勁循環

3.當再也不知足上面的條件,就說明他們已經在一條重鏈中了。那麼天然的,深度更小,在上面的節點爲他們的lca。

那麼,順着這個思路,咱們在跳太重鏈的時候順便query一下整個重鏈,一直query到lca。就完成了整條路徑上的查詢。

錯!

 

 

 你會發現,還差那麼一段沒有被覆蓋上(綠色):

 

 那麼,咱們只須要這樣一段指令:

 

 再把x和y相差的那一段區間再查一下就能夠了。

路徑查詢部分代碼:

inline int querylj(int x,int y)
{
    int ans=0;
    while(top[x]!=top[y])//尚未跳到同一個樹鏈上的話
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);//把x做爲深度更深的節點方便處理
        ans+=query(1,1,n,dfn[top[x]],dfn[x])%p;//一次查詢從鏈頭到尾部
        ans%=p;
        x=fa[top[x]];//跳躍
    }
    if(dep[x]>dep[y])swap(x,y);
    ans+=query(1,1,n,dfn[x],dfn[y])%p;//最後一段的查詢
    return ans%p;
}

至於區間路徑修改,一個原理。

就是把query函數換成update區間修改,而後函數沒有返回值罷了。

代碼:

inline void queryupdlj(int x,int y,int k)//註釋就不加了
{
    int ans=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,n,dfn[top[x]],dfn[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,1,n,dfn[x],dfn[y],k);
}

至於子樹修改,更簡單了:

天然而然的,由於dfs續的緣故,咱們不難想到一個子樹內全部的節點的dfs序都是一個連續區間,區間開頭是子樹根節點的dfn,區間長度爲子樹大小size,那麼對應的區間就爲(設根節點爲x的話)x到x+size[x]-1。

因此這就是個簡單的區間修改,一步到位。

inline int queryson(int x)//查詢
{return query(1,1,n,dfn[x],dfn[x]+size[x]-1);}
inline void queryupdson(int x,int k)//修改{update(1,1,n,dfn[x],dfn[x]+size[x]-1,k);}

 

此題代碼:

#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<iostream>
#define N 100003
using namespace std;
void swap(int &x,int &y){int temp=x;x=y,y=temp;}
int read()
{
    int ans=0;
    char ch=getchar(),last=' ';
    while(ch<'0'||ch>'9')last=ch,ch=getchar();
    while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
    return last=='-'?-ans:ans;
}
struct edg{
    int next,to;
}edge[N*2];
int n,fr,to,m,r,p,a0[N],a[N],a1[N*6],son[N],top[N],size[N],fa[N],dep[N],dfn[N],num1,num2,hea[N],lazy[N*6];
inline void add(int from,int to){num1++;edge[num1]=(edg){hea[from],to};hea[from]=num1;}
inline void pushdown(int rt,int lenn){
    lazy[rt<<1]+=lazy[rt]%p;
    lazy[rt<<1|1]+=lazy[rt]%p;
    a1[rt<<1]+=lazy[rt]*(lenn-(lenn>>1))%p;
    a1[rt<<1|1]+=lazy[rt]*(lenn>>1)%p;
    a1[rt<<1]%=p;
    a1[rt<<1|1]%=p;
    lazy[rt]=0;
}
inline void build(int l,int r,int now)
{
    if(l==r)
    {
        a1[now]=a0[l];a1[now]%=p;return;
    }
    int mid=(l+r)>>1;
    build(l,mid,now<<1);
    build(mid+1,r,now<<1|1);
    a1[now]=(a1[now<<1]+a1[now<<1|1])%p;
}
void update(int now,int l,int r,int L,int R,int k)
{
    if(l>=L&&r<=R){lazy[now]+=k%p;a1[now]+=k*(r-l+1)%p,a1[now]%=p;return;}
    if(lazy[now])pushdown(now,r-l+1);
    int mid=(l+r)>>1;
    if(L<=mid)update(now<<1,l,mid,L,R,k);
    if(R>mid)update(now<<1|1,mid+1,r,L,R,k);
    a1[now]=(a1[now<<1]+a1[now<<1|1])%p;
//  printf("1次upd\n");
}
inline int query(int now,int l,int r,int L,int R)
{
    int ans=0;
    if(l>=L&&r<=R){ans+=a1[now]%p,ans%=p;return ans;}
    if(lazy[now])pushdown(now,r-l+1);
    int mid=(l+r)>>1;
    if(L<=mid)ans+=query(now<<1,l,mid,L,R),ans%=p;
    if(R>mid)ans+=query(now<<1|1,mid+1,r,L,R),ans%=p;
    return ans;
//  printf("1次query\n");
}
inline void dfs1(int now,int f,int deep)
{
    dep[now]=deep;
    fa[now]=f;
    size[now]=1;
    int maxn=-1,maxson=0;
    for(int i=hea[now];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==f)continue;
        dfs1(v,now,deep+1);
        size[now]+=size[v];
        if(size[v]>maxn)maxson=v,maxn=size[v];
    }
    son[now]=maxson;
}
inline void dfs2(int now,int ttop)
{
    dfn[now]=++num2;
    a0[num2]=a[now];
    top[now]=ttop;
    if(!son[now])return;
    dfs2(son[now],ttop);
    for(int i=hea[now];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa[now]||v==son[now])continue;
        dfs2(v,v);
    }
}
inline int querylj(int x,int y)
{
    int ans=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        ans+=query(1,1,n,dfn[top[x]],dfn[x])%p;
        ans%=p;
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    ans+=query(1,1,n,dfn[x],dfn[y])%p;
    return ans%p;
}
inline void queryupdlj(int x,int y,int k)
{
    int ans=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,n,dfn[top[x]],dfn[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    update(1,1,n,dfn[x],dfn[y],k);
}
inline int queryson(int x){return query(1,1,n,dfn[x],dfn[x]+size[x]-1);}
inline void queryupdson(int x,int k){update(1,1,n,dfn[x],dfn[x]+size[x]-1,k);}
int main()
{
    n=read(),m=read(),r=read(),p=read();
    for(int i=1;i<=n;i++)
        a[i]=read();
    for(int i=1;i<=n-1;i++)
    {
        fr=read(),to=read();
        add(fr,to);add(to,fr);
    }
    dfs1(r,0,1);
    dfs2(r,r);
    build(1,n,1);
    for(int i=1;i<=m;i++)
    {
        int k,x1,y1,z1;
        k=read();
        if(k==1){
            x1=read(),y1=read(),z1=read();
            queryupdlj(x1,y1,z1);
        }
        else if(k==2){
           x1=read();y1=read();
            printf("%d\n",querylj(x1,y1)%p);
        }
        else if(k==3){
            x1=read();y1=read();
            queryupdson(x1,y1);
        }
        else{
            x1=read();
            printf("%d\n",queryson(x1)%p);
        }
    }
}

心力交猝。。

完結。

但願對各位有所幫助,有什麼不對的地方評論指出。

相關文章
相關標籤/搜索