樹的重心

注:文章爲博主原創,從git上的博客搬運而來, 原地址

簡介

樹的重心也叫樹的質心。找到一個點,其全部的子樹中最大的子樹節點數最少,那麼這個點就是這棵樹的重心,刪去重心後,生成的多棵樹儘量平衡。git

背景

給樹作分治的時候,爲了獲得最佳的劃分,讓這棵樹更加平衡。算法

定義

定義一:

樹的重心也叫樹的質心。對於一棵樹n個節點的無根樹,找到一個點,使得把樹變成以該點爲根的有根樹時,最大子樹的結點樹最小。換句話說,刪除這個點後最大連通塊(必定是樹)的結點數最小。

定義二:

以這個點爲根,那麼全部的子樹(不算整個樹自身)的大小都不超過整個樹大小的一半

含義

不少博客使用了上述兩種定義,但只是說了定義,沒有講來歷,今天這裏就來分析一番。
上面給出了平衡這個詞語,什麼叫作平衡呢?
什麼樣的樹就看起來更加平衡呢?
定義
$$k_i = n - 2P_i $$
$P$ 表示把這棵樹(無根樹)以 某節點(假設編號爲i)轉化爲有根樹的後,節點數最大的子樹的節點數(下同)
$n$ 表示總節點數
$k$ 表示平衡度
變形爲
$$ k_i = n - 2P_i $$
$$ k _ i = n - P_i - (n - O_i - 1) $$
$$ k_i = O_i - P_i + 1$$
$O$表示除了節點最多子樹和 i 自己之外其餘節點數的總和
$k$ 越大,這棵樹能夠被認爲是更加平衡
那麼給出定義:spa

$k$ 不小於0的時候,該點就是樹的重心

對於定義二,這是顯然的,由於$2P \leq n$,因此$ k = n - 2P \geq 0 $code

對於定義一,它就顯得不那麼好證實了,
因此這裏也給出證實:
充分性:
假設以這個點 $i$ 爲根,轉化爲有根樹,則會獲得:
開始的圖
$P$,$O$的含義同上。
定義一這裏的$P$必定是對於全部其餘節點而言的$P$最小的。
考慮最大子樹的根 maxv ,假設去掉maxv
則會獲得:
去掉了maxvrem

考慮這個此時對於$maxv$來講的$P$
有:
狀況1:新的 $P'$是原子樹$maxv$中的子樹。
狀況2:新的 $P'$是 $O+1$get

對於狀況1:若是新的$P'$是原子樹中的子樹,設這個子樹的節點數爲$P_m$,則有$$P_m = P-1$$,而定義一知足的條件是$P$小於其餘任意一個的$P'$,這明顯不知足條件。博客

對於狀況2:新的$P'$是$O+1$,
$ P' \geq P $
$ O + 1 \geq P$
$ O - P + 1 \geq 0$
$ k \geq 0$
得證。string

必要性:
$k \geq 0$
$O + 1 \geq P$
考慮當前節點$i$ 的 最大子樹節點樹$P$
考慮最大節點子樹$P$的節點,從該節點往下,每個節點的子孫節點都有大於等於以 $i$爲子樹的$O+1$個節點,這個節點的$P'$大於等於$P$。
vroot
考慮節點$i$非最大子樹的節點,這些節點都至少有以$i$爲根的$P+1$個節點,這些節點的$P'$值必定大於$P$
othroot
綜上所述,對於$i$,它的最大子樹是全部節點中最小的
得證。
因此說,其實本質上而言,兩種定義都是對平衡度的限制,it

求法:

樹形DP,dfs一次便可,保存每一個節點的子樹節點數,計算出後計算最大子樹$P$和$O$,取全部點中最小的便可.
代碼(POJ 1655):io

#include<cstdio>
#include<cstring>
#define emax(a,b) ((a) < (b) ? (b) : (a))
using namespace std;
const int MAXN = 20005;
const int MAXE = 40005;
const int INF = 0x3f3f3f3f;
struct edge{
    int v,next;
};
edge e[MAXE];
int head[MAXN],subTree[MAXN],dp[MAXN];
int T,k,n,maxw,maxu;
char inc;

inline void adde(int u,int v){
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}

inline void read(int & x){
    x = 0;
    inc = getchar();
    while(inc < '0' || inc > '9'){
        inc = getchar();
    }
    while(inc >= '0' && inc <= '9'){
        x = (x << 3) + (x << 1) + (inc ^ 48);
        inc = getchar();
    }
}

void dfs(int u,int fa){
    subTree[u] = 1;
    for(int i = head[u];~i;i = e[i].next){
        int v = e[i].v;
        if(v == fa) continue;
        dfs(v,u);
        subTree[u] += subTree[v];
        dp[u] = emax(dp[u],subTree[v]); 
    }
    dp[u] = emax(dp[u],n - subTree[u]);
}

int main(){
    read(T);
    while(T--){
        read(n);
        memset(head,-1,sizeof(head));
        k = 1;
        memset(dp,0,sizeof(dp));
        for(int i = 2;i <= n;i++){
            int u,v;
            read(u);read(v);
            adde(u,v);
            adde(v,u);
        }
        dfs(1,0);
        maxw = INF;
        maxu = -1;
        for(int i = 1;i <= n;i++){
            if(dp[i] < maxw){
                maxu = i;
                maxw = dp[i];
            }
        }
        printf("%d %d\n",maxu,maxw);
    }
    return 0;
}

性質

1.定義距離和值爲
$$S_j = \sum D_{i,j}$$
其中$D_{i,j}$表示$i$到$j$的距離
那麼對於重心,它的 $S_j$ 值最小。若是有兩個重心,他們的 $S_j$ 值相同
2.把兩個樹經過一條邊相連獲得一個新的樹,那麼新的樹的重心在鏈接原來兩個樹的重心的路徑上。
3.把一個樹添加或刪除一個葉子,那麼它的重心最多隻移動一條邊的距離。
4.以u爲根的一顆樹,若是有某兒子:該兒子的子樹節點個數和的二倍>以u爲根的樹的節點的個數,那麼重心在以v爲根的子樹之中。

四個性質都很好證實(畫個圖就能看出),這裏不加贅述

注:對於性質1,參考算法導論中對中位數的絕對值和最小的證實

性質的運用

參見Codeforeces 686D
題目大意是讓你求出這棵樹全部子樹的重心。
根據性質2,只須要從$maxv$爲根的重心不斷向上,直到找到重心爲止:

#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 300005;
const int MAXE = 600005;

struct edge{
    int v,next;
};

edge e[MAXE];
char inc;
int subTree[MAXN],ans[MAXN],fa[MAXN],head[MAXN];
int k,n,q;

inline void adde(int u,int v){
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}



inline void read(int & x){
    x = 0;
    inc = getchar();
    while(inc < '0' || inc > '9'){
        inc = getchar();
    }
    while(inc >= '0' && inc <= '9'){
        x = (x << 3) + (x << 1) + (inc ^ 48);
        inc = getchar();
    }
}

void dfs(int u){
    subTree[u] = 1;
    ans[u] = u;
    int maxSubTree = -1;
    int maxSubTreev = -1;
    for(int i = head[u];~i;i = e[i].next){
        int v = e[i].v;
        dfs(v);
        if(maxSubTree < subTree[v] ){
            maxSubTree = subTree[v];
            maxSubTreev = v;
        }
        subTree[u] += subTree[v];
    }
    if(maxSubTree * 2 <= subTree[u]) return;
    int c = ans[maxSubTreev];
    while(subTree[u] > subTree[c] * 2){
        c = fa[c];
    }
    ans[u] = c;
}


int main(){
    memset(head,-1,sizeof(head));
    read(n);read(q);
    for(int i = 2;i <= n;i++){
        int v;
        read(v);
        //adde(i,v);
        adde(v,i);
        fa[i] = v;
    }
    dfs(1);
    for(int i = 1;i <= q;i++){
        int qu;
        read(qu);
        printf("%d\n",ans[qu]);
    }
    return 0;
};
相關文章
相關標籤/搜索