树的直径的定义
树的直径定义为树上距离最大的两个点的距离。常见的求法有两种。
方法一: 两次DFS
两次DFS的求法比较好理解,写在前面。
证明一个结论:树上任意一个结点能到达的最远的结点,一定是树的直径的一个端点。
设任意结点为 s ,s 能到达的最远节点为 t。他们的距离为
d
i
s
[
s
]
[
t
]
dis[s][t]
dis[s][t]
利用反证法,假设树的直径的两个端点为 n1,n2 且(
n
1
!
=
t
且
n
2
!
=
t
n1!=t且n2!=t
n1!=t且n2!=t)树上直径为
d
i
s
[
n
1
]
[
n
2
]
dis[n1][n2]
dis[n1][n2],t 与 n2 必然存在公共祖先 V。有
d
i
s
[
s
]
[
t
]
=
d
i
s
[
s
]
[
V
]
+
d
i
s
[
V
]
[
t
]
dis[s][t]=dis[s][V]+dis[V][t]
dis[s][t]=dis[s][V]+dis[V][t],
d
i
s
[
n
1
]
[
n
2
]
=
d
i
s
[
n
1
]
[
V
]
+
d
i
s
[
V
]
[
n
2
]
dis[n1][n2]=dis[n1][V]+dis[V][n2]
dis[n1][n2]=dis[n1][V]+dis[V][n2]。
由n1,n2是树上直径可知:
d
i
s
[
V
]
[
n
2
]
>
d
i
s
[
V
]
[
t
]
dis[V][n2]>dis[V][t]
dis[V][n2]>dis[V][t],所以
d
i
s
[
s
]
[
V
]
+
d
i
s
[
V
]
[
n
2
]
>
d
i
s
[
s
]
[
V
]
+
d
i
s
[
V
]
[
t
]
dis[s][V]+dis[V][n2]>dis[s][V]+dis[V][t]
dis[s][V]+dis[V][n2]>dis[s][V]+dis[V][t],所以
d
i
s
[
s
]
[
n
2
]
>
d
i
s
[
s
]
[
t
]
dis[s][n2]>dis[s][t]
dis[s][n2]>dis[s][t] 与 t 是 s 能到达的最远节点矛盾,原命题成立。
知道这个结论再求树的直径就简单了,先随意找一个节点进行DFS,找到最远节点,再对这个最远节点进行一次DFS,再次找最远结点,两个最远节点就是直径的两个端点。
代码:
void dfs(int u, int fa, int d)
{
dis[u] = d;
if(dis[u]>maxdis)
{
maxdis = dis[u];
node1 = u; //node1保存树的直径的一个端点
}
for(int i=head[u];i;i=edge[i].next)
{
int v = edge[i].to;
if(v==fa)
continue;
dfs(v, u, d+edge[i].dis);
}
}
方法二:树上DP
我们假设树的根是rt
那么树的直径显然会有两种情况
- 直径的两个端点分别在rt的两个子树内
- 直径在rt的某个子树内
这里就可以用到分治的思想,先以rt为根处理情况1,再递归rt的子树将情况2转变为情况1处理
用
d
p
[
u
]
dp[u]
dp[u]表示从结点u出发向u的子树走能走到的最远距离
设
v
i
vi
vi为 u 的子节点。
那么有
d
p
[
u
]
=
m
a
x
(
d
p
[
v
i
]
+
d
i
s
(
u
,
v
i
)
)
dp[u]=max(dp[v_i]+dis(u, v_i))
dp[u]=max(dp[vi]+dis(u,vi))
对于以u为根的子树内,经过结点u的最长链长度 m x l e n [ u ] mxlen[u] mxlen[u],有 m x l e n [ u ] = m a x ( d p [ v i ] + d p [ v j ] + d i s ( u , v i ) + d i s ( u , v j ) ) mxlen[u]=max(dp[v_i]+dp[v_j]+dis(u, v_i)+dis(u, v_j)) mxlen[u]=max(dp[vi]+dp[vj]+dis(u,vi)+dis(u,vj))
由于dfs的特性,这里我们并不需要
O
(
n
2
)
O(n^2)
O(n2)枚举,假设当前搜到的
u
u
u 的子节点为
v
i
v_i
vi,那么显然
d
p
[
u
]
dp[u]
dp[u]已经被
d
p
[
v
1
]
到
d
p
[
v
i
−
1
]
dp[v_1]到dp[v_{i-1}]
dp[v1]到dp[vi−1]中最大的一个更新过,所以这里可以直接用已有的
d
p
[
u
]
dp[u]
dp[u] 代替另一个子节点的枚举。
最后整个树的最长链就是
m
a
x
(
m
x
l
e
n
(
u
)
)
,
(
1
<
=
u
<
=
N
)
max(mxlen(u)) ,(1<=u<=N)
max(mxlen(u)),(1<=u<=N)
代码:
void DP(int u, int pa)
{
dp[u] = 0;
for(int i=head[u];i;i=edge[i].next)
{
int v = edge[i].to;
if(v==pa)
continue;
DP(v, u);
mxlen = max(mxlen, dp[u]+dp[v]+edge[i].dis);
dp[u] = max(dp[u], dp[v]+edge[i].dis);
}
}
例题
模板1: POJ 2631 Roads in the North.
模板2: POJ 1985 Cow Marathon.
树的直径+思维: 洛谷P5536 【XR-3】核心城市.