设将要合并的两个连通块为 $A,B$,新添加的边为 $(i,j)$(其中 $i \in A$,$j \in B$),新连通块为 $S$。
考虑连上 $(i,j)$ 边后,$S$ 连通块直径是怎样构成的。可以分为三种情况:
- $A$ 连通块的直径;
- $B$ 连通块的直径;
- $A$ 连通块里从 $i$ 点出发的最远路径 + $(i,j)$ 这一条边 + $B$ 连通块里从 $j$ 点出发的最远路径。
实现的时候,因为 $n \leq 150$,可以用 Floyd 算法求出任意两点间最短路径和从任意一点出发的最远路径,然后用并查集维护连通块和每个连通块的直径。
随后枚举不在同一连通块内的两个点 $i,j$,取上面三种情况中的最大值作为新连通块的直径即可。
时间复杂度为 $O(n^3)$。
需要注意的是,有不少做法(包括《信息学竞赛一本通》(第三版)上介绍的做法,不知道这个问题在后面再版的时候有没有修正)只考虑了上面的情况 3,简单地用这条路径作为新连通块的直径,这样的做法显然是错误的。但原数据较弱,这种错解可以通过所有数据。
// Problem: P1522 [USACO2.4]牛的旅行 Cow Tours
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1522
// Memory Limit: 128 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
struct dsu {
int fa[155], n;
void init(int n) {
this->n = n;
for (int i = 1; i <= n; i++) fa[i] = i;
}
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return;
fa[y] = x;
}
bool together(int x, int y) { return find(x) == find(y); }
} ds;
struct point {
int x, y;
} p[155];
char str[155];
double d[155][155], maxd[155], ad[155];
double dist(double x1, double y1, double x2, double y2) {
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
int main() {
int n;
scanf("%d", &n);
ds.init(n);
for (int i = 1; i <= n; i++) scanf("%d%d", &p[i].x, &p[i].y);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
int c;
scanf("%1d", &c);
if (i == j)
d[i][j] = 0;
else if (c == 1) {
d[i][j] = dist(p[i].x, p[i].y, p[j].x, p[j].y);
ds.merge(i, j);
} else
d[i][j] = 1 << 30;
}
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
if (ds.together(i, j)) maxd[i] = max(maxd[i], d[i][j]);
ad[ds.find(i)] = max(ad[ds.find(i)], maxd[i]);
}
double ans = 1 << 30;
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
if (!ds.together(i, j))
ans = min(ans,
max(maxd[i] + maxd[j] + dist(p[i].x, p[i].y, p[j].x, p[j].y),
max(ad[ds.find(i)], ad[ds.find(j)])));
printf("%.6lf\n", ans);
return 0;
}