题意
题解
树中最远两点间的距离为树的直径。树上各直径的中点(不一定恰好是某个节点,可能在某条边的内部)是唯一的。
D F S DFS DFS 求解某条树的直径,找到直径的中点,即可将各直径划分为两段进行求解。设 c n t [ x ] cnt[x] cnt[x] 为节点 x x x 的儿子中可以到达直径在这一侧的长度的数量。若直径中点为节点 x x x,当 c n t [ x ] ≥ 3 cnt[x]\geq 3 cnt[x]≥3,树的直径无必须边;若直径中点在边 ( x , y ) (x,y) (x,y) 内部,则边 ( x , y ) (x,y) (x,y) 为树的直径的必须边。
上述两种情况分别用点或边将树划分为数个连通分量,在各连通分量中 D F S DFS DFS 求解必须边即可。具体而言,从划分位置的必须边向 c n t [ x ] > 0 cnt[x]>0 cnt[x]>0 的节点(子树中存在直径的部分路径)进行搜索,当 c n t [ x ] ≥ 2 cnt[x]\geq 2 cnt[x]≥2 时(子树上不存在必须边)终止搜索,搜索过的边即树的直径的必须边。总时间复杂度 O ( N ) O(N) O(N)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 200005;
int N, res, pre[maxn], cnt[maxn];
int tot, head[maxn], to[maxn << 1], nxt[maxn << 1], cost[maxn << 1];
ll ds[maxn], mx[maxn];
inline void add(int x, int y, int z)
{
to[++tot] = y, cost[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
void dfs(int x, int f, ll w, int &t)
{
ds[x] = w;
if (ds[x] >= ds[t])
t = x;
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i], z = cost[i];
if (y != f)
dfs(y, x, w + z, t), pre[y] = x;
}
}
void dfs2(int x, int f, ll w, ll t)
{
mx[x] = w, cnt[x] = w == t;
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i], z = cost[i];
if (y != f)
dfs2(y, x, w + z, t), mx[x] = max(mx[x], mx[y]), cnt[x] += mx[y] == t;
}
}
void dfs3(int x, int f)
{
++res;
if (cnt[x] > 1)
return;
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i];
if (y != f && cnt[y])
dfs3(y, x);
}
}
int main()
{
scanf("%d", &N);
for (int i = 1, x, y, z; i < N; ++i)
scanf("%d%d%d", &x, &y, &z), add(x, y, z), add(y, x, z);
int s = 1, t = 0;
dfs(s, 0, 0, t);
s = t, t = 0;
dfs(s, 0, 0, t);
ll dm = ds[t];
int p;
for (p = t; ds[pre[p]] * 2 > dm; p = pre[p])
;
if (ds[pre[p]] * 2 == dm)
{
memset(cnt, 0, sizeof(cnt));
dfs2(pre[p], 0, 0, dm / 2);
if (cnt[pre[p]] < 3)
for (int i = head[pre[p]]; i; i = nxt[i])
{
int y = to[i];
if (cnt[y])
dfs3(y, pre[p]);
}
}
else
{
--res;
memset(cnt, 0, sizeof(cnt));
dfs2(pre[p], p, 0, ds[pre[p]]);
dfs3(pre[p], p);
memset(cnt, 0, sizeof(cnt));
dfs2(p, pre[p], 0, dm - ds[p]);
dfs3(p, pre[p]);
}
printf("%lld\n%d\n", dm, res);
return 0;
}
设直径的两个端点为 u , v u,v u,v,根据树的直径的最长性,任何从 u , p u,p u,p 之间分叉离开直径的子树,其最远点与 p p p 的距离都不会比 u u u 与 p p p 的距离更远。
根据直径的最长性,可以更高效地求解树的直径的必须边。设 s ⋯ t s\cdots t s⋯t 为树的某条直径,设 x x x 为直径上的一点, L [ x ] , R [ x ] L[x],R[x] L[x],R[x] 分别代表 s ⋯ x s\cdots x s⋯x 与 x ⋯ t x\cdots t x⋯t 的长度。根据必须边的性质,其一定处于任一直径上,那么处理 s ⋯ t s\cdots t s⋯t 即可。
当直径上某个节点可以引出 1 1 1 条长度等于与直径端点距离的分叉,那么这个节点至端点上的边都不可能是必须边。 D F S DFS DFS 求解直径上各节点到子树端点的最远近距离,设其为 m x [ x ] mx[x] mx[x]。从 s s s 向 t t t 扫描,找到第一个满足 R [ x ] = m x [ x ] R[x]=mx[x] R[x]=mx[x] 的点 r r r,再从 r r r 向 s s s 扫描找到第一个满足 L [ x ] = m x [ x ] L[x]=mx[x] L[x]=mx[x] 的位置 l l l,此时区间 [ l , r ] [l,r] [l,r] 左右两侧的边都不可能是必须边,答案为 [ l , r ] [l,r] [l,r] 间的边数。总时间复杂度 O ( N ) O(N) O(N)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 200005;
int N, res, pre[maxn], num, rec[maxn];
int tot, head[maxn], to[maxn << 1], nxt[maxn << 1], cost[maxn << 1];
ll ds[maxn], mx[maxn];
bool in[maxn];
inline void add(int x, int y, int z)
{
to[++tot] = y, cost[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
void dfs(int x, int f, ll w, int &t)
{
ds[x] = w, pre[x] = f;
if (ds[x] >= ds[t])
t = x;
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i], z = cost[i];
if (y != f)
dfs(y, x, w + z, t);
}
}
void dfs2(int x, int f, ll w)
{
mx[x] = w;
for (int i = head[x]; i; i = nxt[i])
{
int y = to[i], z = cost[i];
if (y != f && !in[y])
dfs2(y, x, w + z), mx[x] = max(mx[x], mx[y]);
}
}
int main()
{
scanf("%d", &N);
for (int i = 1, x, y, z; i < N; ++i)
scanf("%d%d%d", &x, &y, &z), add(x, y, z), add(y, x, z);
int s = 1, t = 0;
dfs(s, 0, 0, t);
s = t, t = 0;
dfs(s, 0, 0, t);
ll dm = ds[t];
for (int i = t; i; i = pre[i])
rec[++num] = i, in[i] = 1;
for (int i = 1; i <= N; ++i)
if (in[i])
dfs2(i, 0, 0);
int l, r;
for (r = 1; r <= num && ds[rec[r]] > mx[rec[r]]; ++r)
;
for (l = r; l && dm - ds[rec[l]] > mx[rec[l]]; --l)
;
printf("%lld\n%d\n", dm, r - l);
return 0;
}