注:文章为博主原创,从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
则会获得:rem
考虑这个此时对于$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$。
考虑节点$i$非最大子树的节点,这些节点都至少有以$i$为根的$P+1$个节点,这些节点的$P'$值必定大于$P$
综上所述,对于$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; };