2/3树的直径等内容 学习总结
树的直径值树中最长的简单路径,一般用两次dfs和树形dp求,是数据结构中常见常考的重点之一。
两次dfs求树的直径
求法
从任意点dfs一次,找到距离该点最远的点,记为点 \(s\)。
从 \(s\) 出发再dfs一次,找到距离最远的点,记为点 \(t\)。
\(s\) 到 \(t\) 的简单路径一定是树的直径之一。
证明

如上图
根据定义,不存在 \(u\) 使得 \(|u\rightarrow s|>|t\rightarrow s|\)
如果存在节点 \(u\),使得 \(|u\rightarrow t|>|s\rightarrow t|\)
那么 \(|n\rightarrow u|>|n\rightarrow s|\),故 \(|1\rightarrow u|>|1\rightarrow s|\)
第一次搜索就会搜到 \(u\) 而不是 \(s\)。
故假设矛盾,所以不存在任何一点,到 \(s\) 或者 到 \(t\) 的距离,比 \(|s\rightarrow t|\) 更远 。
证毕
注意事项
1.必须全是非负权边
\(\mathscr{A\rightarrow^{10}B\rightarrow^{-1000}C\rightarrow^{1000}D}\)
如果搜索的起点选择了 \(\mathscr{D}\),可以求出直径。但是如果选择了 \(\mathscr{B}\),则求不出直径。
代码
1.有多个dfs/bfs时,函数名不能是其他函数名的前缀,变量名同理。
$\surd$
void dfs1(...)
void dfs2(...)
$\times$(打错了就很难调)
void dfs(...)
void dfs2(...)
AC code
#include<bits/stdc++.h>
using namespace std;
int d1[100005],d2[100005];
vector<int> g[100005];
void dfs1(int x,int fa){
for(auto y:g[x]){
if(y==fa) continue;
d1[y]=d1[x]+1;
dfs1(y,x);
}
}
void dfs2(int x,int fa){
for(auto y:g[x]){
if(y==fa) continue;
d2[y]=d2[x]+1;
dfs2(y,x);
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int n;
cin>>n;
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs1(1,0);
int maxn=0,s=0;
for(int i=1;i<=n;i++){
if(d1[i]>=maxn){
s=i;
maxn=d1[i];
}
}
dfs2(s,0);
int maxm=0,t=0;
for(int i=1;i<=n;i++){
if(d2[i]>=maxm){
t=i;
maxm=d2[i];
}
}
cout<<d2[t];
return 0;
}
树形dp求树的直径
求法
记录 \(d1[u]\) 为以 \(u\) 为根的子树中,从 \(u\) 出发向下的最长路径。
\(d2[u]\) 为以 \(u\) 为根的子树中,从 \(u\) 出发向下的次长路径。(\(\Large{不与最长路径重合!}\))
遍历时 \(u\) 的儿子 \(v\) 会贡献长度 \(len=d1[v]+w(u,v)\) 的路径,此时:
如果 \(len>d1[u]\),说明找到了更长的路径,\(d2[u]=d1[u]\),\(d1[u]=len\)。
否则如果 \(len>d2[u]\),说明比次长的长,比最长的短,只需更新次长的。\(d2[u]=len\)。
答案:$$\max^{n}_{u=1}\lbrace d1[u]+d2[u]\rbrace$$
核心代码
inline void dfs(int u, int fa) {//当前的节点点, 当前节点的祖先
for(int i = head[u]; ~i; i = e[i].nxt) {//链式前向星
int v = e[i].to;
if(v == fa) continue;//防止向上走, 这是个常用的小技巧, 不需要记录vis数组
dfs(v, u);//后序遍历
if(dp[u][0] < dp[v][0] + 1) { dp[u][1] = dp[u][0]; dp[u][0] = dp[v][0] + 1; }//dp[u][1]得到了除了v以外的最大值 + 1
else if(dp[u][1] < dp[v][0] + 1) dp[u][1] = dp[v][0] + 1;//确保dp[u][0]没有被更新
res = max(res, dp[u][0] + dp[u][1]);//更新答案
}
}
几个定理
Kőnig 定理
二分图的最小点覆盖数 \(=\) 二分图的最大匹配数
证明没讲
Gallai 恒等式:
\(∣\large{最小点覆盖}∣+∣\large{最大独立集}∣=∣V|\)
为什么?跟定义有关。
如果你选出了一个点覆盖集合 \(S\),那么剩下的所有点之间 \(V∖S\) 之间
一定没有任何边相连(如果有边相连,那条边就没有被 \(S\) 中的点覆盖,这与 \(S\) 是点覆盖矛
盾)。因此,\(V∖S\) 是一个独立集。
所以,如果一个很难求,可以求另一个简单点的来推出难求的。
收获
-
学习了树的直径的两种求法
-
学习了两个定理

树形dp-树的直径总结
浙公网安备 33010602011771号