快餐店[NOI2013]
题目描述
小 T 打算在城市 C 开设一家外送快餐店。送餐到某一个地点的时间与外卖店到该地点之间最短路径长度是成正比的,小 T 希望快餐店的地址选在离最远的顾客距离最近的地方。
快餐店的顾客分布在城市 C 的 \(N\) 个建筑中,这 \(N\) 个建筑通过恰好 \(N\) 条双向道路连接起来,不存在任何两条道路连接了相同的两个建筑。任意两个建筑之间至少存在一条由双向道路连接而成的路径。小 T 的快餐店可以开设在任一建筑中,也可以开设在任意一条道路的某个位置上(该位置与道路两端的建筑的距离不一定是整数)。
现给定城市 C 的地图(道路分布及其长度),请找出最佳的快餐店选址,输出其与最远的顾客之间的距离。
题解
首先考虑一棵树上答案是怎样的:答案应该是树直径的 \(\dfrac{1}{2}\) ,即快餐店取在直径一半处,证明如下:
如果快餐店不在直径的中点处,而是在某点 \(X\) ,假设直径是 \(A\rightarrow B\) ,长度为 \(d\),那么树上离 \(X\) 最远的点要么是 \(A\) 要么是 \(B\) ,只有 \(\operatorname{dis}(A,X)<\dfrac{d}{2}\) 且 \(\operatorname{dis}(B,X)<\dfrac{d}{2}\) 时 \(X\) 才更优,两式相加得 \(\operatorname{dis}(A,X)+\operatorname{dis}(B,X)<\operatorname{dis}(A,B)\) ,显然这是不可能的
那么如果是基环树要怎么做呢?
考虑断掉基环树环上的某条边,使其变成一棵树,求出这棵树的直径长为 \(d\),则表示有一种取址方案使得答案为 \(\dfrac{d}{2}\)
所以最终答案就是枚举环上断哪条边求得的所有 \(\dfrac{d}{2}\) 的最小值
那么现在需要快速算出断某条边后树的直径
先找出环上的所有点,按顺序给每个点标号 \(1\sim cnt\)
首先,对于不经过环的那些路径,无论怎么断边它的长度都不会变,可以先在环上每个点的子树里dfs求得其中最长的那条,记为P
算出环上每个点距离环上第1个点的距离,记为len[i]
,算出环上每个点子树内距它最远的点到它的距离,记为mx[i]
预处理出bef[i]
\(=\max\limits_{1\le j\le i}\)len[j]+mx[j]
,aft[i]
\(=\max\limits_{i\le j\le cnt}\)(len[cnt]-len[j])+mx[j]
bef2[i]
\(=\max\limits_{1\le j < k \le i}\)mx[j]+mx[k]+(len[k]-len[j])
,aft2[i]
\(=\max\limits_{i\le j < k \le cnt}\)mx[j]+mx[k]+(len[k]-len[j])
记环上第一个点和最后一个点之间的边的长度是D
假设断开的是环上第 \(i\) 个点到第 \(i+1\) 个点之间的边,那么此时树的直径长就是 \(\max(\)bef2[i], aft2[i+1], bef[i]+aft[i+1]+D, P
\()\)
求出所有直径长取min,再除以2即为答案
代码
#include <bits/stdc++.h>
#define N 1000005
#define E(i) int i = head[x]; i; i = pre[i]
using namespace std;
typedef long long ll;
template<typename T>
inline void read(T &num) {
T x = 0, _f = 1; char ch = getchar();
for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') _f = -1;
for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
num = x * _f;
}
int n, head[N], to[N<<1], pre[N<<1], sz;
ll val[N<<1], ans, Ans, len[N], Mx[N];
int stk[N], top, c[N], tot;
bool vis[N], onc[N], flg;
inline void addedge(int u, int v, ll w) {
pre[++sz] = head[u]; head[u] = sz; to[sz] = v; val[sz] = w;
pre[++sz] = head[v]; head[v] = sz; to[sz] = u; val[sz] = w;
}
void getcir(int x, int fa) {
stk[++top] = x; vis[x] = 1;
for (E(i)) {
int y = to[i];
if (y == fa) continue;
if (vis[y]) {
flg = 1;
while (stk[top] != y) c[++tot] = stk[top--];
c[++tot] = y;
return;
} else {
getcir(y, x);
if (flg) return;
}
}
top--;
}
ll f[N], bef[N], aft[N], bef2[N], aft2[N];
void dfs1(int x, int fa) { //求直径
for (E(i)) {
int y = to[i];
if (y == fa || onc[y]) continue;
dfs1(y, x);
ans = max(ans, f[y] + f[x] + val[i]); //直径不在环上
f[x] = max(f[x], f[y] + val[i]);
}
}
void solve(int id) {
dfs1(c[id], 0);
Mx[id] = f[c[id]];
}
void DP() {
ll now = 0, mx = 0;
for (int i = 1; i <= tot; i++) {
bef[i] = max(bef[i-1], now + Mx[i]);
if (i > 1) bef2[i] = max(bef2[i-1], mx + now + Mx[i]);
mx = max(mx, Mx[i] - now);
now += len[i];
}
now = mx = 0;
for (int i = tot; i; i--) {
aft[i] = max(aft[i+1], now + Mx[i]);
if (i < tot) aft2[i] = max(aft2[i+1], mx + now + Mx[i]);
mx = max(mx, Mx[i] - now);
now += len[i-1];
}
Ans = min(Ans, bef2[tot]);
for (int i = 1; i < tot; i++) {
ll ccf = max(max(bef2[i], aft2[i+1]), bef[i] + aft[i+1] + len[tot]);
ccf = max(ccf, ans);
Ans = min(Ans, ccf);
}
}
int main() {
read(n); Ans = 114514114514114514;
for (int i = 1; i <= n; i++) {
int u, v; ll w;
read(u); read(v); read(w);
addedge(u, v, w);
}
getcir(1, 0);
for (int i = 1; i <= tot; i++) onc[c[i]] = 1;
for (int i = 1; i <= tot; i++) {
int x = c[i];
for (E(j)) if (to[j] == c[i%tot+1]) len[i] = val[j];
}
for (int i = 1; i <= tot; i++) {
solve(i);
}
DP();
printf("%.1lf\n", Ans / 2.0);
return 0;
}