1. 程式人生 > 實用技巧 >樹鏈剖分詳解&題解 P6098 【[USACO19FEB]Cow Land G】

樹鏈剖分詳解&題解 P6098 【[USACO19FEB]Cow Land G】

看到各位大佬們已經把其他的東西講的很明白了,我這個 juruo 就講一講最基本的樹鏈剖分吧。

0.樹剖是什麼?能吃嗎?

不能吃

樹剖是樹鏈剖分的簡稱,我們一般說的樹剖其實指重鏈剖分。當然,還有一種長鏈剖分我不會,但是據說不常用。

樹鏈剖分能夠把樹剖分成許多鏈,這樣就可以用維護區間的方式維護一棵樹。

1.怎麼剖分

先引入一些概念:

  1. 重兒子:一棵樹最大的子樹叫重兒子。例如這棵樹中3就是1的重兒子:很明顯,一棵樹的重兒子是唯一的。什麼?有多棵子樹的大小相同?那就隨便選一個唄。
  2. 輕兒子:除了重兒子都是輕兒子。廢話
  3. 重邊:連線父親和重兒子的邊就是重邊。
  4. 輕邊:除了重邊都是輕邊。
  5. 重鏈:許多重邊連起來就叫重鏈。例如:

這棵樹裡節點 \(\{1,3,5,6\}\) 可以構成一顆重鏈。很顯然每個重鏈的起點一定是一個輕兒子。每個節點都屬於且僅屬於一條重鏈。<-很重要,一定要記住!

然後就開始剖分了。

具體的剖分過程,就是維護一些陣列:

  • \(deep[i]\) 代表節點 \(i\) 的深度。
  • \(top[i]\) 代表節點 \(i\) 所屬重鏈的鏈頂。(也就是重鏈裡深度最小的那個節點)
  • \(size[i]\) 代表以 \(i\) 為根的子樹的大小。
  • \(son[i]\) 代表節點 \(i\) 的唯一一個重兒子是誰。
  • \(f[i]\) 代表節點 \(i\) 的父親是誰。
  • \(dfn[i]\) 代表節點 \(i\)
    的”遍歷順序“。

剖分時要跑兩個dfs。經典操作

第一個dfs要維護 \(size\)\(son\)\(f\)\(deep\) 這幾個陣列。

提示:樹要用無向圖存!

void dfs1(int u,int fa/*記錄當前節點父親是誰*/){
    size[u]=1;//因為自己也是子樹的一部分
    f[u]=fa;
    deep[u]=deep[fa]+1;//很明顯,當前深度=父親深度+1
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];//遍歷每個出邊
        if(v!=fa){//如果當前出邊終點是兒子而不是父親
            dfs1(v,u);//搜
            size[u]+=size[v];//加上兒子大小
            if(size[v]>size[son[u]]){//找到最大的兒子作為重兒子
                son[u]=v;
            }
        }
    }
}

然後我們已經知道了每個節點的重兒子,現在應該把它們連起來成為一條重鏈了:

void dfs2(int u,int tp/*當前鏈頂*/){
    top[u]=tp;
    dfn[u]=++step;
    if(son[u]){//如果沒有重兒子,那麼一個兒子也沒有
        dfs2(son[u],tp);//優先遍歷重兒子,為什麼之後再說
        for(int i=0;i<g[u].size();i++){
            int v=g[u][i];
            if(son[u]!=v&&f[u]!=v){//遍歷輕兒子
                dfs2(v,v);//輕兒子一定是一條重鏈的鏈頂
            }
        }
    }
}

如果優先遍歷重兒子,那麼重鏈的\(dfn\)一定是連續的。例如:

因為重鏈的\(dfn\)是連續的,而每個點都屬於一條重鏈,所以可以用線段樹維護區間的方式維護點權,這樣就不用暴力的一個個查,一個個改了。

一些常見的用法:

query(1,1,n,dfn[top[u]],dfn[u])//查詢u到鏈頂的點權和
modify(1,1,n,dfn[top[u]],dfn[u],3)//把u到鏈頂的點權都加3

具體到題目上,可以發現甚至連懶惰標記都不需要,沒有區間修改的操作。

那麼,怎麼計算從一個點到另外一個點路徑上的點權和?

int query_ans(int u,int v){
    int ret=0;
    while(top[u]!=top[v]){
        if(deep[top[u]]<deep[top[v]]){//注意,一定要比較鏈頂深度!坑了我好幾次
            swap(u,v);
        }
        ret^=query(1,1,n,dfn[top[u]],dfn[u]);//這道題要求異或
        u=f[top[u]];
    }//就是當uv不在同一條鏈上時,讓鏈頂深度小的往上跳
    if(deep[u]>deep[v]){
        swap(u,v);
    }
    ret^=query(1,1,n,dfn[u],dfn[v]);//當在同一條鏈上時,把它們之間的點加起來
    return ret;
}

知道了這些操作,這題就非常好寫了。就是直接把板子套上去嘛!

AC Code:

#include <bits/stdc++.h>
using namespace std;
#define MAXN 200005
int n,q,e[MAXN];
vector<int> g[MAXN];
int dfn[MAXN],step,top[MAXN],size[MAXN],son[MAXN],f[MAXN],deep[MAXN];
void dfs1(int u,int fa){
    size[u]=1;
    f[u]=fa;
    deep[u]=deep[fa]+1;
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v!=fa){
            dfs1(v,u);
            size[u]+=size[v];
            if(size[v]>size[son[u]]){
                son[u]=v;
            }
        }
    }
}
void dfs2(int u,int tp){
    top[u]=tp;
    dfn[u]=++step;
    if(son[u]){
        dfs2(son[u],tp);
        for(int i=0;i<g[u].size();i++){
            int v=g[u][i];
            if(son[u]!=v&&f[u]!=v){
                dfs2(v,v);
            }
        }
    }
}
int tree[MAXN*4];
void push_up(int rt){
    tree[rt]=tree[rt*2]^tree[rt*2+1];
}
void modify(int rt,int l,int r,int x,int k){
    if(l==r){
        tree[rt]=k;
    }else{
        int mid=(l+r)/2;
        if(x<=mid){
            modify(rt*2,l,mid,x,k);
        }else{
            modify(rt*2+1,mid+1,r,x,k);
        }
        push_up(rt);
    }
}
int query(int rt,int l,int r,int L,int R){
    if(L>R){return 0;}
    if(L<=l&&R>=r){
        return tree[rt];
    }else{
        int mid=(l+r)/2,ret=0;
        if(L<=mid){
            ret^=query(rt*2,l,mid,L,R);
        }
        if(R>mid){
            ret^=query(rt*2+1,mid+1,r,L,R);
        }
        return ret;
    }
}
int query_ans(int u,int v){
    int ret=0;
    while(top[u]!=top[v]){
        if(deep[top[u]]<deep[top[v]]){
            swap(u,v);
        }
        ret^=query(1,1,n,dfn[top[u]],dfn[u]);
        u=f[top[u]];
    }
    if(deep[u]>deep[v]){
        swap(u,v);
    }
    ret^=query(1,1,n,dfn[u],dfn[v]);
    return ret;
}
int main(){
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++){
        scanf("%d",e+i);
    }
    for(int i=1;i<=n-1;i++){
        int u,v,w;
        scanf("%d%d",&u,&v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,1);
    for(int i=1;i<=n;i++){
        modify(1,1,n,dfn[i],e[i]);
    }
    for(int i=1;i<=q;i++){
        int op;
        scanf("%d",&op);
        if(op==1){
            int x,k;
            scanf("%d%d",&x,&k);
            modify(1,1,n,dfn[x],k);
        }else{
            int u,v;
            scanf("%d%d",&u,&v);
            printf("%d\n",query_ans(u,v));
        }
    }
    return 0;
}

完結撒花~