Page Top

ZOJ3820 Building Fire Stations 题解

ZOJ3820 Building Fire Stations 题解

题目链接

原 OJ(卡空间):https://pintia.cn/problem-sets/91827364500/exam/problems/91827369872

或者 Codeforces GYM(空间足够):https://codeforces.com/gym/100554/problem/B

有翻译+我自己出的数据(不卡空间):https://www.luogu.com.cn/problem/U370080

题目简述

一棵树,在其中找两个点,使得其他点到这两个的距离的较小值的最大值的最小值及其方案。

分析

先考虑选一个点的情况

树的中心模板,可以见我的帖子:https://www.cnblogs.com/RainPPR/p/tree-dp.html

两个点呢

假设我们已经选出了两个点 \((a,b)\) 那么,显然,树中所有的点要么去 \(a\) 要么去 \(b\),且一定有一条边作为分界线;即有一条边 \((p,q)\) 其两边子树中的点,都去 \(a\) 或都去 \(b\);且点 \(a\) 和点 \(b\) 一定不在同一棵子树中。

因此,我们就可以选点 \(a\)\(p\) 一侧的子树的中心,点 \(b\)\(q\) 一侧的子树的中心。然后考虑 \((p,q)\) 怎么选?(感性理解)可以大胆猜测,边 \((p,q)\) 一定是树的直径中,中间的那一条边!

为什么呢?与我在上面那篇帖子中树的中心的证明类似:一棵树上到点 \(x\) 最远的点一定是其直径 \((S,T)\) 的一个端点,根据三角不等式 \(\text{dis}(S,x)+\text{dis}(x,T)\ge\text{dis}(S,T)\),所以 \(\max\{\text{dis}(S,x),\text{dis}(x,T)\}\ge\frac{1}{2}\text{dis}(S,T)\),而等号是在 \(x\)\((S,T)\) 中点时取到。

我们可以得出:\(\max\{\text{dis}(S,p),\text{dis}(q,T)\}\le\frac{1}{2}\text{dis}(S,T)\quad(\text{map}_{p,q}=1)\),因此有 \((p,q)\)\((S,T)\) 上最中间的一段。

证明结束。

程序

注意,原数据会爆栈,所以不能用 DFS,可以换成 BFS(但是 Codeforces GYM 上空间加了一倍,到了 \(256\text{ MB}\),这就卡卡能过了)。

代码:

#include <bits/stdc++.h>

using namespace std;

#define rr read()
inline int read() {
    int num = 0, flag = 1;
    char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') flag = -1;
    for (; isdigit(ch); ch = getchar()) num = num * 10 + ch - '0';
    return num * flag;
}

#define tp(t) template<typename t>

#define max _max
tp(t) inline t _max(const t a, const t b) { return a > b ? a : b; }
#define min _min
tp(t) inline t _min(const t a, const t b) { return a < b ? a : b; }
#define abs _abs
tp(t) inline t _abs(const t x) { return x < 0 ? -x : x; }

using pii = pair<int, int>;
using tii = tuple<int, int>;

const int N = 2e5 + 10;

int n;
vector<int> g[N];

void add(int u, int v) { g[u].push_back(v), g[v].push_back(u); }

int lc, rc;
int d[N], f[N];

#define get find_bfs::find  // 在这里切换 DFS 和 BFS
namespace find_bfs {        // 返回距离 u 最远的点
    pii q[N]; int st, ed;
    int find(int u) {
        d[u] = 0, f[u] = -1; st = 0, ed = 0, q[ed++] = {u, -1};
        int c = u; while (st < ed) {
            pii now = q[st++]; int u = now.first;
            for (int v : g[u]) if (v != now.second) {
                if ((u == lc && v == rc) || (u == rc && v == lc)) continue;
                q[ed++] = {v, u}; f[v] = u;
                if ((d[v] = d[u] + 1) > d[c]) c = v;
        }} return c;
    }
}; namespace find_dfs {     // 返回距离 u 最远的点
    int c; void dfs(int u, int fa) {
        for (int v : g[u]) if (v != fa) {
            if ((u == lc && v == rc) || (u == rc && v == lc)) continue;
            if ((d[v] = d[u] + 1) > d[c]) c = v; dfs(v, u); f[v] = u;
    }} int find(int u) {
        d[u] = 0, f[u] = -1, c = u;
        dfs(u, -1); return c;
    }
}

int main() {
    int T = rr; while (T--) {
        n = rr; for (int i = 1; i < n; ++i) add(rr, rr);
        lc = rc = -1; int s = get(1), e = get(s);
        lc = e, rc = f[e]; for (int i = 1; i < d[e]; i += 2) tie(lc, rc) = make_tuple(rc, f[rc]);
        int le = get(get(lc)), ls = d[le] + 1; for (int i = d[le]; i > 1; i -= 2) le = f[le];
        int re = get(get(rc)), rs = d[re] + 1; for (int i = d[re]; i > 1; i -= 2) re = f[re];
        printf("%d %d %d\n", max(ls, rs) >> 1, le, re);
        for (int i = 1; i <= n + 1; ++i) g[i].clear();
    }
    return 0;
}
posted @ 2023-10-13 17:00  RainPPR  阅读(29)  评论(0编辑  收藏  举报