树的重心定义
树的重心也叫树的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个点后最大连通块(一定是树)的结点数最小。
-------百度百科
可以类比一下物理上的重心,一个物体本来质量是不均匀的,但是在重心这个质点上就可以把这个物体视为均匀的 (口胡) 那么树的重心可以这样理解:以这个点为根节点,它的多棵子树 “尽可能平衡”。
树的重心的性质
一下性质摘抄自百度百科。
- 树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个重心,他们的距离和一样。
- 把两棵树通过一条边相连,新的树的重心在原来两棵树重心的连线上。
- 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
- 一棵树最多有两个重心,且相邻。
树的重心的求法
第一种方法是根据定义求树的重心。
size[ i ]表示 i 节点的子树大小(自定义根节点),dp[ i ]表示以 i 为根节点的最大子树大小。
由于要找一个结点的最大字数,所以这个算法的过程很类似树链剖分求重儿子的过程,然后注意一点,对于一个结点u,如果把 u 作为根节点,他的子树不止包含当前(钦定了另一个根节点后)它的所有子树,还包括他的祖先的那一个分支,这一部分的大小也应该参与比较。
代码:
void dfs(int u, int fa)
{
size[u] = 1;
dp[u] = 0;
for(int i=head[u];i;i=edge[i].next)
{
int v = edge[i].to;
if(v==fa)
continue;
dfs(v, u);
size[u] += size[v];
dp[u] = max(dp[u], size[v]);
}
dp[u] = max(dp[u], N-size[u]); //上面提到的处理
if(dp[u]<dp[ans])
ans = u;
}
也可以用重心性质求树的重心
利用性质:树中所有点到某个点的距离和中,到重心的距离和最小。
还是先指定一个根节点,把无根树变成有根树。size[ i ]同上。
deep[ i ]为结点 i 的深度。dis[ i ]为所有点到点 i 的距离和。
定义 subtree(x) 表示以 x 为根的子树中点的集合。显然 subtree(x)∈n
那么对于树上的非根节点 u,设它的父亲为 v。
所以转移方程
d
i
s
[
u
]
=
d
i
s
[
v
]
+
(
N
−
s
i
z
e
[
u
]
)
−
s
i
z
e
[
u
]
=
d
i
s
[
v
]
+
N
−
2
∗
s
i
z
e
[
u
]
dis[u]=dis[v]+(N-size[u])-size[u]=dis[v]+N-2*size[u]
dis[u]=dis[v]+(N−size[u])−size[u]=dis[v]+N−2∗size[u]
转移方程的解释:
- 考虑不在 subtree(x) 中的点,它们到 u 的距离和是 它们到 v 的距离和加上 (N-size[u])
- 而对于那些在 subtree(u) 中的点,它们到 u 的距离和就是 它们到 v 的距离和再减去 (size[u])
所以合并两式, d i s [ u ] = d i s [ v ] + N − 2 ∗ s i z e [ u ] dis[u]=dis[v]+N-2*size[u] dis[u]=dis[v]+N−2∗size[u]
代码:
void dfs1(int u)
{
size[u] = 1;
for(int i=head[u];i;i=edge[i].next)
{
int v = edge[i].to;
if(deep[v])
continue;
deep[v] = deep[u]+1;
dfs1(v);
size[u] += size[v];
}
}
void dfs(int u, int fa)
{
dis[u] = dis[fa]+N-2*size[u];
for(int i=head[u];i;i=edge[i].next)
{
int v = edge[i].to;
if(v==fa)
continue;
dfs(v, u);
}
}
例题
模板: 洛谷P1395 会议.
找两个重心:POJ 1655 Balancing Act.