树的重心

注:文章为博主原创,从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;
};
相关文章
相关标签/搜索