「BZOJ 3242」「NOI 2013」快餐店「基环树」

题意

基环树上找到一个点(可以在边上)使得它到树上最远点的距离最小,输出最小距离

题解

如果是一棵树,答案就是树的直径\(/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}\)

我觉得这是一个错误的解法,回头有时间更新一种用前后缀的做法。

如果我几个月都没更 可以在评论里捶我

posted @ 2019-02-11 20:35  hfhongzy  阅读(152)  评论(0编辑  收藏  举报