[C++] P1099 [NOIP 2007 提高组] 树网的核

T = (V, E, W) 是一个无圈且连通的无向图(也称为无根树),每条边都有正整数的权,我们称 T 为树网(treenetwork),其中 VE 分别表示结点与边的集合,W 表示各边长度的集合,并设 Tn 个结点。

路径:树网中任何两结点 ab 都存在唯一的一条简单路径,用 d(a, b) 表示以 a, b 为端点的路径的长度,它是该路径上各边长度之和。我们称 d(a, b)a, b 两结点间的距离。

D(v, P) = min {d(v, u)}, u 为路径 P 上的结点。

树网的直径:树网中最长的路径称为树网的直径。对于给定的树网 T,直径不一定是唯一的,但可以证明:各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。

偏心距 ECC(F):树网 T 中距路径 F 最远的结点到路径 F 的距离,即

ECC(F) = max {D(v, F), v ∈ V}

任务:对于给定的树网 T = (V, E, W) 和非负整数 s,求一个路径 F,他是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过 s(可以等于 s),使偏心距 ECC(F) 最小。我们称这个路径为树网 T = (V, E, W) 的核(Core)。必要时,F 可以退化为某个结点。一般来说,在上述定义下,核不一定只有一个,但最小偏心距是唯一的。

下面的图给出了树网的一个实例。图中,A − BA − C 是两条直径,长度均为 20。点 W 是树网的中心,EF 边的长度为 5。如果指定 s = 11,则树网的核为路径DEFG(也可以取为路径DEF),偏心距为 8。如果指定 s = 0(或 s = 1s = 2),则树网的核为结点 F,偏心距为 12

输入格式

n 行。

1 行,两个正整数 ns,中间用一个空格隔开。其中 n 为树网结点的个数,s 为树网的核的长度的上界。设结点编号以此为 1, 2…, n

从第 2 行到第 n 行,每行给出 3 个用空格隔开的正整数 u, v, w,依次表示每一条边的两个端点编号和长度。例如,2 4 7 表示连接结点 24 的边的长度为 7

输出格式

一个非负整数,为指定意义下的最小偏心距。

输入输出样例 #1

输入 #1

1
2
3
4
5
5 2
1 2 5
2 3 2
2 4 4
2 5 3

输出 #1

1
5

输入输出样例 #2

输入 #2

1
2
3
4
5
6
7
8
8 6
1 3 2
2 3 2
3 4 6
4 5 3
4 6 4
4 7 2
7 8 3

输出 #2

1
5

说明/提示

  • 对于 40% 的数据,保证 n ≤ 15
  • 对于 70% 的数据,保证 n ≤ 80
  • 对于 100% 的数据,保证 2 ≤ n ≤ 3000 ≤ s ≤ 1031 ≤ u, v ≤ n0 ≤ w ≤ 103

NOIP2007 提高组第四题

题解

  1. 输入处理
    • 读取节点数n和最大允许路径长度s
    • 读取并构建树的邻接表表示
  2. 寻找树的直径
    • getFarthest(int start):从起点出发找到最远的节点
    • buildDiameter():通过两次DFS找到树的最长路径(直径)
  3. 预处理距离信息
    • 计算每个节点到直径上每个点的距离,存储在g[][]数组中
    • 计算直径上相邻点之间的累积距离,存储在segLen[]
  4. 枚举所有可能的路径
    • 枚举直径上所有可能的子路径[l, r]
    • 检查路径长度是否≤s
    • 对于每个有效路径,计算其偏心距(到其他节点的最大最小距离)
    • 记录最小的偏心距
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;

const int MAXN = 305;
const int INF = 1e9;

struct Edge {
int to, w;
};

vector<Edge> adj[MAXN];
int dist[MAXN], pre[MAXN];
int diameter[MAXN], dlen;
int f[MAXN][MAXN]; // f[i][j] = min distance from node i to diameter segment [0,j]
int n, s;

void dfs(int u, int p, int d) {
dist[u] = d;
pre[u] = p;
for (auto& e : adj[u]) {
if (e.to != p) {
dfs(e.to, u, d + e.w);
}
}
}

int getFarthest(int start) {
memset(dist, 0, sizeof(dist));
memset(pre, -1, sizeof(pre));
dfs(start, -1, 0);

int res = start, maxd = 0;
for (int i = 1; i <= n; i++) {
if (dist[i] > maxd) {
maxd = dist[i];
res = i;
}
}
return res;
}

void buildDiameter() {
int u = getFarthest(1);
int v = getFarthest(u);

dlen = 0;
int cur = v;
while (cur != -1) {
diameter[dlen++] = cur;
cur = pre[cur];
}
reverse(diameter, diameter + dlen);
}

int getDistance(int u, int v) {
memset(dist, 0x3f, sizeof(dist));
dfs(u, -1, 0);
return dist[v];
}

int main() {
scanf("%d %d", &n, &s);

for (int i = 0; i < n - 1; i++) {
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}

buildDiameter();

// 预处理:计算每个点到直径上每个点的距离
int g[MAXN][MAXN];
for (int i = 0; i < dlen; i++) {
memset(dist, 0x3f, sizeof(dist));
dfs(diameter[i], -1, 0);
for (int j = 1; j <= n; j++) {
g[j][i] = dist[j];
}
}

// 计算直径上相邻点的距离
int segLen[MAXN] = {0};
for (int i = 1; i < dlen; i++) {
segLen[i] = segLen[i-1] + getDistance(diameter[i-1], diameter[i]);
}

int ans = INF;

// 枚举所有可能的核心路径
for (int l = 0; l < dlen; l++) {
for (int r = l; r < dlen; r++) {
int pathLen = segLen[r] - segLen[l];
if (pathLen > s) break;

int ecc = 0;
for (int v = 1; v <= n; v++) {
int minDist = INF;
for (int i = l; i <= r; i++) {
minDist = min(minDist, g[v][i]);
}
ecc = max(ecc, minDist);
}
ans = min(ans, ecc);
}
}

printf("%d\n", ans);
return 0;
}