\(\text{Part I: 树的直径}\)
定义:树上最远的两个点。
大致做法:两次 \(\text{dfs}\) .
第一次我们对任意一个节点跑 \(\text{dfs}\) 求出书上离他最远的点设为 q
则下一次我们以 q
为根再跑一遍 \(\text{dfs}\) 到达另一个最远结点。最后可以证明这两个节点就是树的直径。
代码:
//树的直径
void dfs(int u, int fa) {//u: 当前搜索到的节点 fa 这个节点的父亲节点
for (int v : E[u]) {//枚举出边
if (v == fa) continue;//如果这个出边回到他的父亲,那么过掉
d[v] = d[u] + 1;
if (d[v] > d[c]) c = v;//更新
dfs(v, u);//继续往下跑
}
}
\(\text{Part II: 树的重心}\)
大致做法:继续跑 \(\text{dfs}\) .
在 \(\text{dfs}\) 中计算每个子树的大小,记录“向下”的子树的最大大小,利用总点数 - 当前子树(这里的子树指有根树的子树)的大小得到“向上”的子树的大小,然后就可以依据定义找到重心了。
但其实这句话不好理解,感觉看代码会好很多。
//树的重心
void GetCentroid(int cur, int fa) { //cur 和 fa 分别是当前搜索到的节点和父亲节点
size[cur] = 1;//无论如何这个节点的大小至少是1吧
weight[cur] = 0;//暂时初始化的时候这个点没有子树
for (int i = head[cur]; i != -1; i = e[i].nxt) {//开始遍历
if (e[i].to != fa) { // e[i].to 表示这条有向边所通向的节点。
GetCentroid(e[i].to, cur);//继续往下搜索
size[cur] += size[e[i].to];//因为有子树了嘛,所以大小要直接加上他那个子树的大小
weight[cur] = max(weight[cur], size[e[i].to]);//取个最大值算重量
}
}
weight[cur] = max(weight[cur], n - size[cur]);//不好解释,就是相当于有一部分会没有被记录到,即这个节点“向上看”的子树,然而也就这一个没有记录到,因此我们直接把当前的最大值和 n减去当前的大小做过比较就可以求出来了。
if (weight[cur] <= n / 2) { // 依照树的重心的定义统计
centroid[centroid[0] != 0] = cur;
}
}