注:文章为博主原创,从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$ 越大,这棵树可以被认为是更加平衡
那么给出定义:
$k$ 不小于0的时候,该点就是树的重心
对于定义二
,这是显然的,因为$2P \leq n$,所以$ k = n - 2P \geq 0 $
对于定义一
,它就显得不那么好证明了,
所以这里也给出证明:
充分性:
假设以这个点 $i$ 为根,转化为有根树,则会得到:
$P$,$O$的含义同上。
由定义一
这里的$P$一定是对于所有其他节点而言的$P$最小的。
考虑最大子树的根 maxv ,假设去掉maxv
则会得到:
考虑这个此时对于$maxv$来说的$P$
有:
情况1:新的 $P'$是原子树$maxv$中的子树。
情况2:新的 $P'$是 $O+1$
对于情况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$
得证。
必要性:
$k \geq 0$
$O + 1 \geq P$
考虑当前节点$i$ 的 最大子树节点树$P$
考虑最大节点子树$P$的节点,从该节点往下,每一个节点的子孙节点都有大于等于以 $i$为子树的$O+1$个节点,这个节点的$P'$大于等于$P$。
考虑节点$i$非最大子树的节点,这些节点都至少有以$i$为根的$P+1$个节点,这些节点的$P'$值一定大于$P$
综上所述,对于$i$,它的最大子树是所有节点中最小的
得证。
所以说,其实本质上而言,两种定义都是对平衡度的限制,
求法:
树形DP,dfs一次即可,保存每个节点的子树节点数,计算出后计算最大子树$P$和$O$,取所有点中最小的即可.
代码(POJ 1655):
#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;
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。