[題解] [SDOI2011] 消防

wxy3265發表於2024-10-03

[題解] [SDOI2011] 消防

tag: 圖論樹的直徑
題目連結(洛谷):https://www.luogu.com.cn/problem/P2491

題目描述

給定一棵 \(n\) 個節點的樹,第 \(i\) 條邊有邊權 \(z_i\) 。要求找到樹上一條長度不大於 \(s\) 的簡單路徑,使得不在路徑上的點到該路徑的最大距離最小。
資料範圍:\(1 \leq n \leq 3 \times 10^5, 0 \leq z_i \leq 10^3\)

題解

不難得出結論:如果沒有 \(s\) 的限制,則選擇該樹的直徑最優。進一步考慮長度為 \(s\) 的限制對答案的影響,可以發現即使在限制長度的條件下,答案的路徑依然是直徑的子集。在此不做嚴格證明,可以嘗試採用在直徑上刪邊、在點上擴充套件邊的方式驗證。因此,我們列舉在直徑中的路徑區間來解決此問題。對於直徑,可以使用兩次 DFS 的方式求得直徑上的所有點,及其中各相鄰點之間的距離,這個過程的時間複雜度是 \(O(n)\)。對於區間列舉,我們使用雙指標,從左到右列舉區間的左端點,利用 \(l \leq s\) 的條件確定右端點即可,這個過程的時間複雜度也是 \(O(n)\)

那麼新的問題是,對於一條確定的直徑上的路徑,如何求得距離該路徑最遠的點的距離。我們按照將直徑縮短的思路思考。最初始的情況,我們要考慮不在直徑上的點到直徑的距離,可以利用 DFS ,從每個直徑上的點向不在直徑上的點遍歷,在 \(O(n)\) 的時間複雜度內求出每個不在直徑上的點到直徑的最大值,我們稱之為初始最長路徑,這些初始最長路徑的最大長度就是該問題的初始答案。接下來考慮直徑縮短,我們又可以得到一個新的結論:在路徑從直徑上縮短時,除初始最長路徑之外,新的最長路徑一定是縮短後的路徑的兩端到直徑的兩端的距離的較大值。這個結論也不難驗證。因此,我們只需在雙指標列舉時動態維護這個長度,同時更新直徑上的答案即可。最終的答案是初始答案和直徑上的較大值。

AC 程式碼

//
// Created by wxy3265 on 2024/10/3.
//
#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
using namespace std;

const int MAXN = 2e7 + 5e6;
const int MAXM = 2e7 + 5e6;

// 建圖
int first[MAXN], v[MAXM], nt[MAXM], w[MAXM];
int te;
inline void addEdge(int x, int y, int ww) {
    nt[++te] = first[x];
    first[x] = te;
    v[te] = y;
    w[te] = ww;
}

// 第一次 DFS 找到直徑的一端
int maxd, maxdi;
void dfs1(int x, int fa, int step) {
    if (step > maxd) maxd = step, maxdi = x;
    for (int eo = first[x]; eo; eo = nt[eo]) {
        const int ep = v[eo];
        if (ep == fa) continue;
        dfs1(ep, x, step + w[eo]);
    }
}

// 第二次 DFS 找到直徑,同時求出直徑上相鄰點及距離(nxt、nw)
int f[MAXN], nxt[MAXN], nw[MAXN];
void dfs2(int x, int fa, int step) {
    for (int eo = first[x]; eo; eo = nt[eo]) {
        const int ep = v[eo];
        if (ep == fa) continue;
        dfs2(ep, x, step + w[eo]);
        if (f[ep] > f[x]) {
            f[x] = f[ep];
            nxt[x] = ep;
            nw[x] = w[eo];
        }
    }
    if (!f[x]) f[x] = step;
}

// 第三次 DFS 求出從各直徑上的點出發不經過其他直徑上的點的最遠距離
bool id[MAXN];
int mx[MAXN];
void dfs3(int x, int fa) {
    for (int eo = first[x]; eo; eo = nt[eo]) {
        const int ep = v[eo];
        if (ep == fa || id[ep]) continue;
        dfs3(ep, x);
        mx[x] = max(mx[x], mx[ep] + w[eo]);
    }
}

int q[MAXN], head = 0, tail = 0;
signed main() {
    // 讀入資料
    int n, s;
    cin >> n >> s;
    for (int i = 1; i <= n - 1; i++) {
        int x, y, ww;
        cin >> x >> y >> ww;
        addEdge(x, y, ww);
        addEdge(y, x, ww);
    }
    // 求直徑
    dfs1(1, 0, 0);
    dfs2(maxdi, 0, 0);
    int tot = 0;
    head = 1;
    for (int i = maxdi; i != 0; i = nxt[i]) {
        q[++tail] = i;
        tot += nw[i];
        id[i] = true;
    }
    // 求初始答案
    int r = tail, now = tot, ans = 0, tmp = tot;
    for (int i = 1; i <= n; i++) {
        if (id[i]) {
            dfs3(i, 0);
            ans = max(ans, mx[i]);
        }
    }
    // 雙指標遍歷
    int dr = 0, dl = 0;
    while (head <= s) {
        while (r >= head && now > s) {
            r--;
            now -= nw[q[r]];
            dr += nw[q[r]];
        }
        tmp = min(tmp, max(dr, dl));
        dl += nw[q[head]];
        now -= nw[q[head]];
        head++;
        while (r <= tail && now + nw[q[r]] <= s) {
            now += nw[q[r]];
            dr -= nw[q[r]];
            r++;
        }
    }
    cout << max(ans, tmp) << '\n';
    return 0;
}

相關文章