[NOIP2007 提高组] 树网的核
题目描述
设
T
=
(
V
,
E
,
W
)
T=(V,E,W)
T=(V,E,W) 是一个无圈且连通的无向图(也称为无根树),每条边都有正整数的权,我们称
T
T
T 为树网(treenetwork
),其中
V
V
V,
E
E
E 分别表示结点与边的集合,
W
W
W 表示各边长度的集合,并设
T
T
T 有
n
n
n 个结点。
路径:树网中任何两结点
a
a
a,
b
b
b 都存在唯一的一条简单路径,用
d
(
a
,
b
)
d(a, b)
d(a,b) 表示以
a
,
b
a, b
a,b 为端点的路径的长度,它是该路径上各边长度之和。我们称
d
(
a
,
b
)
d(a, b)
d(a,b) 为
a
,
b
a, b
a,b 两结点间的距离。
D ( v , P ) = min { d ( v , u ) } D(v, P)=\min\{d(v, u)\} D(v,P)=min{d(v,u)}, u u u 为路径 P P P 上的结点。
树网的直径:树网中最长的路径成为树网的直径。对于给定的树网 T T T,直径不一定是唯一的,但可以证明:各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。
偏心距 E C C ( F ) \mathrm{ECC}(F) ECC(F):树网 T T T 中距路径 F F F 最远的结点到路径 F F F 的距离,即
E C C ( F ) = max { D ( v , F ) , v ∈ V } \mathrm{ECC}(F)=\max\{D(v, F),v \in V\} ECC(F)=max{D(v,F),v∈V}
任务:对于给定的树网
T
=
(
V
,
E
,
W
)
T=(V, E, W)
T=(V,E,W) 和非负整数
s
s
s,求一个路径
F
F
F,他是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过
s
s
s(可以等于
s
s
s),使偏心距
E
C
C
(
F
)
\mathrm{ECC}(F)
ECC(F) 最小。我们称这个路径为树网
T
=
(
V
,
E
,
W
)
T=(V, E, W)
T=(V,E,W) 的核(Core
)。必要时,
F
F
F 可以退化为某个结点。一般来说,在上述定义下,核不一定只有一个,但最小偏心距是唯一的。
下面的图给出了树网的一个实例。图中,
A
−
B
A-B
A−B 与
A
−
C
A-C
A−C 是两条直径,长度均为
20
20
20。点
W
W
W 是树网的中心,
E
F
EF
EF 边的长度为
5
5
5。如果指定
s
=
11
s=11
s=11,则树网的核为路径DEFG
(也可以取为路径DEF
),偏心距为
8
8
8。如果指定
s
=
0
s=0
s=0(或
s
=
1
s=1
s=1、
s
=
2
s=2
s=2),则树网的核为结点
F
F
F,偏心距为
12
12
12。
输入格式
共 n n n 行。
第 1 1 1 行,两个正整数 n n n 和 s s s,中间用一个空格隔开。其中 n n n 为树网结点的个数, s s s 为树网的核的长度的上界。设结点编号以此为 1 , 2 … , n 1,2\dots,n 1,2…,n。
从第
2
2
2 行到第
n
n
n 行,每行给出
3
3
3 个用空格隔开的正整数
u
,
v
,
w
u, v, w
u,v,w,依次表示每一条边的两个端点编号和长度。例如,2 4 7
表示连接结点
2
2
2 与
4
4
4 的边的长度为
7
7
7。
输出格式
一个非负整数,为指定意义下的最小偏心距。
样例 #1
样例输入 #1
5 2
1 2 5
2 3 2
2 4 4
2 5 3
样例输出 #1
5
样例 #2
样例输入 #2
8 6
1 3 2
2 3 2
3 4 6
4 5 3
4 6 4
4 7 2
7 8 3
样例输出 #2
5
提示
- 对于 40 % 40\% 40% 的数据,保证 n ≤ 15 n \le 15 n≤15。
- 对于 70 % 70\% 70% 的数据,保证 n ≤ 80 n \le 80 n≤80。
- 对于 100 % 100\% 100% 的数据,保证 2 ≤ n ≤ 300 2\le n \le 300 2≤n≤300, 0 ≤ s ≤ 1 0 3 0\le s\le10^3 0≤s≤103, 1 ≤ u , v ≤ n 1 \leq u, v \leq n 1≤u,v≤n, 0 ≤ w ≤ 1 0 3 0 \leq w \leq 10^3 0≤w≤103。
NOIP2007 提高组第四题(洛谷)
题解
#include<cstdio>
#include<algorithm>
#define M 500005
using namespace std;
int n, m, x, y, z, k, id, top, ans = 2e9;
int dis[M], fa[M], head[M];
bool mark[M];
// 边结构体
struct edge {
int to, w, nxt;
} E[M << 1]; // 边的数组,支持无向图
// 添加边的函数
void add(int u, int v, int w) {
E[++id] = ((edge){v, w, head[u]});
head[u] = id; // 存图
}
// 深度优先搜索,计算每个节点到源节点的距离
void dfs(int f, int x) {
fa[x] = f; // 记录父节点
if (dis[x] > dis[k]) k = x; // 找到最远的点
for (int i = head[x]; i; i = E[i].nxt) {
int y = E[i].to;
if (y == f || mark[y]) continue;
dis[y] = dis[x] + E[i].w; // 更新距离
dfs(x, y); // 递归搜索
}
}
int main() {
scanf("%d%d", &n, &m); // 输入节点数和路径最大长度
for (int i = 1; i < n; i++) {
scanf("%d%d%d", &x, &y, &z); // 输入每条边的信息
add(x, y, z), add(y, x, z); // 添加边
}
dis[1] = 1, dfs(0, 1); // 从节点1开始第一次DFS
dis[k] = 0, dfs(0, k); // 从第一个最远点开始第二次DFS
// k表示最远的端点
top = k; // 找直径
for (int i = top, j = top, l = 1, r = 0; i; i = fa[i]) {
while (dis[j] - dis[i] > m) j = fa[j]; // 尺取法,选路径
x = max(dis[top] - dis[j], dis[i]); // 路径两端点到直径端点的最小贡献
ans = min(ans, x);
}
for (int i = top; i; i = fa[i]) mark[i] = 1; // 标记直径,重新计算每个点的贡献
for (int i = top; i; i = fa[i]) {
k = i, dis[k] = 0;
dfs(fa[i], i);
}
for (int i = 1; i <= n; i++)
ans = max(ans, dis[i]); // 每个点的贡献
printf("%d\n", ans);
return 0;
}