1. 程式人生 > >【CodeForces】835F Roads in the Kingdom

【CodeForces】835F Roads in the Kingdom

一、題目

題目描述

王國有\(n\)座城市與\(n\)條有長度的街道,保證所有城市直接或間接聯通,我們定義王國的直徑為所有點對最短距離中的最大值,現因財政危機需拆除一條道路並同時要求所有城市仍然聯通,求所有拆除方案中王國直徑的最小值

輸入格式

第一行一個整數\(n\),接下來\(n\)行每行三個整數\(u,v,w\)表示城市\(u,v\)之間有一條長度為\(w\)的道路

輸出格式

一行一個答案,表示所有方案中直徑最小值

二、題意

定義直徑為任意兩點間的最短距離的最大值。給出一棵基環樹,問刪去環上的一條邊,使剩下的樹的直徑最小。問最小的直徑是多少。

三、PROCESS OF THINKING

我剛開始的時候在想怎麼選擇刪的邊才最優,後來想了一個上午發現好像行不通。所以我往列舉刪除哪條邊,然後快速求出此時的樹的直徑這方面想。

其中有一點可以確定,就是每個環上的點對應的樹是不變的。
應該可以想到求出每個環上的點到其樹中的最長鏈。然後通過環來合併這些鏈。

考慮環上的點,首先我們列舉只能從\(x\)開始順時針走(相當於把\(x\)連向前面的點的邊刪掉)。設\(f_i\)\(i\)這個點對應的鏈長,\(sum_i\)\(i\)\(x\)的距離。
由此可以得出一條路徑可以表示為\[f_i+f_j+sum_j-sum_i\],即為\[f_i-sum_i+sum_j+f_j\]

而對於每種情況我們要求的是最長的路徑。故只要\(f_i-sum_i\)

最大,\(f_j+sum_j\)最大就行了。用兩個set維護就好了。

至於萬一選擇的兩個點\(i\)\(j\)的相對位置的問題,我也思考了一下(可能是我太弱了)。
我們假設\(i\)\(j\)後面,而我們選擇了\(i\)\(f_i+sum_i\)最大的,\(j\)\(f_j-sum_j\)最大的。那麼由於\(sum_i>sum_j\),所以其實\[f_i+sum_i+f_j-sum_j>f_i-sum_i+f_j+sum_j\]的。(哦,這裡提一下,如果選擇的\(i=j\),那麼嘗試\(f_i+sum_i\)取次小值,或\(f_j-sum_j\)取次小值)所以我們只要保證\(f_i+sum_i+f_j-sum_j\)

是最優的且\(i\not=j\),一定可以保證\(i\)\(j\)前面。

這裡還有一個小細節,就是如何列舉下一個開始點時,要把上一個點\(i\)放到陣列的末尾,還要解決\(sum_i\)變大的問題。其實只要把環上的點再重新加到原陣列的末尾就好了,這樣可以結局\(sum_i\)的問題。

四、程式碼


#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <set>
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair <LL, int> PLI;
typedef pair <int, int> PII;
const int MAXN = 200000;
const LL INF = 1e15;
multiset <PLI, greater <PLI> > set1;
multiset <PLI, greater <PLI> > set2;
struct Edge {
    int to, nxt, w;
} edge[MAXN + 5 << 1];
LL ans, mxdis[MAXN + 5], sum[MAXN + 5 << 1], dia;
int fir[MAXN + 5], ecnt, pre[MAXN + 5], eid[MAXN + 5], lpcnt, dfn[MAXN + 5], timer, n;
PII lop[MAXN + 5 << 1];
bool inl[MAXN + 5];
template <typename T> void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); }
    while (c >= '0' && c <= '9') { x = x * 10 + c - 48; c = getchar(); }
    x *= f;
}
void addedge(int u, int v, int w) {
    edge[++ecnt].to = v;
    edge[ecnt].nxt = fir[u];
    edge[ecnt].w = w;
    fir[u] = ecnt;
}
void getloop(int u) {
    dfn[u] = ++timer;
    for (int e = fir[u]; e && !lpcnt; e = edge[e].nxt) {
        int v = edge[e].to;
        if (v == pre[u]) continue;
        if (dfn[v]) {
            if (dfn[v] < dfn[u]) continue;
            for (int i = v; i != u; i = pre[i]) {
                lop[++lpcnt] = make_pair(i, edge[eid[i]].w);
                inl[i] = true;
            }
            lop[++lpcnt] = make_pair(u, edge[e].w);
            inl[u] = true;
            return;
        }
        pre[v] = u;
        eid[v] = e;
        getloop(v);
    }
}
void dfs(int u, int fa) {
    LL maxx = 0, minx = 0;
    mxdis[u] = 0;
    for (int e = fir[u]; e; e = edge[e].nxt) {
        int v = edge[e].to;
        if (v == fa || inl[v]) continue;
        dfs(v, u);
        mxdis[u] = max(mxdis[u], mxdis[v] + edge[e].w);
        if (mxdis[v] + edge[e].w > maxx) {
            minx = maxx;
            maxx = mxdis[v] + edge[e].w;
        } else if (mxdis[v] + edge[e].w > minx)
            minx = mxdis[v] + edge[e].w;
    }
    dia = max(dia, minx + maxx);
}
int main() {
    read(n);
    for (int i = 1; i <= n; ++i) {
        int u, v, w;
        read(u), read(v), read(w);
        addedge(u, v, w);
        addedge(v, u, w);
    }
    getloop(1);
    for (int i = 1; i <= lpcnt; ++i) {
        lop[i + lpcnt] = lop[i];
        dfs(lop[i].fi, 0);
    }
    for (int i = 2; i <= lpcnt << 1; ++i)
        sum[i] = sum[i - 1] + lop[i - 1].se;
    for (int i = 1; i <= lpcnt; ++i) {
        set1.insert(make_pair(mxdis[lop[i].fi] + sum[i], i));
        set2.insert(make_pair(mxdis[lop[i].fi] - sum[i], i));
    }
    ans = INF;
    for (int i = 1; i <= lpcnt; ++i) {
        LL diameter;
        if (set1.begin()->se == set2.begin()->se) {
            multiset <PLI, greater <PLI> >::iterator ptr = set2.begin();
            ++ptr;
            diameter = set1.begin()->first + ptr->first;
            ptr = set1.begin();
            ++ptr;
            diameter = max(diameter, ptr->first + set2.begin()->first);
        } else
            diameter = set1.begin()->first + set2.begin()->first;
        ans = min(ans, diameter);
        set1.erase(make_pair(mxdis[lop[i].fi] + sum[i], i));
        set2.erase(make_pair(mxdis[lop[i].fi] - sum[i], i));
        set1.insert(make_pair(mxdis[lop[i + lpcnt].fi] + sum[i + lpcnt], i + lpcnt));
        set2.insert(make_pair(mxdis[lop[i + lpcnt].fi] - sum[i + lpcnt], i + lpcnt));
    }
    printf("%lld\n", max(ans, dia));
    return 0;
}
  • TIPS
    1. 前面提到樹的直徑是不變的,所以最後的答案不應該超過每個環上的點對應的樹的直徑。
    2. 數組別忘了開兩倍...