1. 程式人生 > >「BZOJ 3242」「NOI 2013」快餐店「基環樹」

「BZOJ 3242」「NOI 2013」快餐店「基環樹」

影響 oid 如果 bzoj type 就是 ans fin dfs

題意

基環樹上找到一個點(可以在邊上)使得它到樹上最遠點的距離最小,輸出最小距離

題解

如果是一棵樹,答案就是樹的直徑\(/2\)

如果是基環樹,那麽很好證明刪去環上的某一條邊是不影響答案的。於是斷環為鏈,單調隊列維護\(dep+sum,dep-sum\)的最大值和次大值,然後算直徑,如果兩個最大值是同個結點就取一個次大,否則都取最大。

#include <algorithm>
#include <cstdio>
using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

struct Edge {
    int v, w, nxt;
} e[N << 1];
int hd[N], p;
void link(int u, int v, int w) {
    e[p] = (Edge) {v, w, hd[u]};
    hd[u] = p ++;
}

int n, dfn[N], idx;
int fa[N], fw[N], c[N], d[N], cnt;
bool cir[N];

void dfs(int u, int cur = -1) {
    dfn[u] = ++ idx;
    for(int i = hd[u]; ~ i; i = e[i].nxt) if(i != cur) {
        int v = e[i].v, w = e[i].w;
        if(!dfn[v]) {
            fa[v] = u; fw[v] = w; dfs(v, i ^ 1);
        } else if(dfn[v] < dfn[u]) {
            cnt ++; cir[c[cnt] = u] = 1; d[cnt] = w;
            for(int j = u; j != v; j = fa[j]) {
                cnt ++; cir[c[cnt] = fa[j]] = 1; d[cnt] = fw[j];
            }
        }
    }
}

ll mdep[N][2], tree_d;

void dfs2(int u, int f = -1) {
    for(int i = hd[u]; ~ i; i = e[i].nxt) {
        int v = e[i].v;
        if(v == f || cir[v]) continue ;
        dfs2(v, u);
        ll dis = e[i].w + mdep[v][0];
        if(dis > mdep[u][0]) {
            mdep[u][1] = mdep[u][0];
            mdep[u][0] = dis;
        } else if(dis > mdep[u][1]) {
            mdep[u][1] = dis;
        }
    }
    tree_d = max(tree_d, mdep[u][0] + mdep[u][1]);
}

int main() {
    scanf("%d", &n);
    fill(hd + 1, hd + n + 1, -1);
    for(int u, v, w, i = 1; i <= n; i ++) {
        scanf("%d%d%d", &u, &v, &w);
        link(u, v, w); link(v, u, w);
    }
    dfs(1);
    static ll dep[N << 1], sum[N << 1], ans = 1ll << 62;
    for(int i = 1; i <= cnt; i ++) {
        dfs2(c[i]);
        dep[i] = dep[i + cnt] = mdep[c[i]][0];
    }
    for(int i = 1; i <= cnt << 1; i ++)
        sum[i] = sum[i - 1] + d[i > cnt ? i - cnt : i];

    static int q1[N << 1], l1, r1;
    static int q2[N << 1], l2, r2;
    #define val1(u) dep[u] - sum[u]
    #define val2(u) dep[u] + sum[u]
    for(int i = 1; i <= cnt << 1; i ++) {
        for(; l1 < r1 && q1[l1] + cnt - 1 < i; l1 ++) ;
        for(; r1 - l1 > 1 && q1[l1 + 1] + cnt - 1 < i; l1 ++) q1[l1 + 1] = q1[l1];
        for(; l2 < r2 && q2[l2] + cnt - 1 < i; l2 ++) ;
        for(; r2 - l2 > 1 && q2[l2 + 1] + cnt - 1 < i; l2 ++) q2[l2 + 1] = q2[l2];

        for(; r1 - l1 > 2 && val1(q1[r1 - 1]) <= val1(i); r1 --) ;
        q1[r1 ++] = i;
        if(r1 - l1 <= 3) {
            for(int j = r1 - 1; j > l1; j --)
                if(val1(q1[j]) > val1(q1[j - 1])) swap(q1[j], q1[j - 1]);
        }
        for(; r2 - l2 > 2 && val2(q2[r2 - 1]) <= val2(i); r2 --) ;
        q2[r2 ++] = i;
        if(r2 - l2 <= 3) {
            for(int j = r2 - 1; j > l2; j --)
                if(val2(q2[j]) > val2(q2[j - 1])) swap(q2[j], q2[j - 1]);
        }
        if(i >= cnt && r1 - l1 > 1) {
            int a1 = q1[l1], a2 = q1[l1 + 1];
            int b1 = q2[l2], b2 = q2[l2 + 1];
            ll cir_d = 0;
            if(a1 == b1) cir_d = max(val1(a1) + val2(b2), val1(a2) + val2(b1));
            else cir_d = val1(a1) + val2(b1);
            ans = min(ans, max(tree_d, cir_d));
        }
    }
    printf("%lld.%c\n", ans >> 1, ans & 1 ? '5' : '0');
    return 0;
}

\(\text{Codeforces 835F}\)是一樣的題,數據範圍乘\(2\),改一下輸出就行.

什麽,\(\text{Wrong Answer}\)

我覺得這是一個錯誤的解法,回頭有時間更新一種用前後綴的做法。

如果我幾個月都沒更 可以在評論裏捶我

「BZOJ 3242」「NOI 2013」快餐店「基環樹」