【GDOI2018模拟9.23】博弈

Description:

这里写图片描述
1<=n<=10^6

题解:

首先策略肯定是把Tom逼到一个点,然后去删边,接着取消标记让Tom一步步走到T那儿去。
Tom肯定会往代价最大的地方钻,1/4可以选择封住最大的先,然后Tom往次大的走。
以b为根,建树。
对于不在b->t的路径上的点,求出w,表示往它的子树中走一圈回来的最小代价,dp显然。
现在你可以想像从b->t走,你可以删掉相邻的子树,但是你不知道删哪棵。
于是二分答案,遵循能删必删的思想,就可以确定了,直到某个深度不行了。

Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

const int N = 1e6 + 5;

int n, s, t, x, y, sum;
int final[N], next[N * 2], to[N * 2], tot;
int w[N], bz[N], sum_son[N], fa[N];
int d[N], bz_d[N];

void link(int x, int y) {
    next[++ tot] = final[x], to[tot] = y, final[x] = tot;
    next[++ tot] = final[y], to[tot] = x, final[y] = tot;
}

void dg(int x) {
    if(x == t) return;
    bz[x] = 1;
    for(int i = final[x]; i; i = next[i]) {
        int y = to[i]; if(bz[y]) continue;
        sum_son[x] ++; fa[y] = x; dg(y);
    }
    bz[x] = 0;
}

void Build(int x) {
    while(x != s) d[++ d[0]] = x, bz_d[x] = 1, x = fa[x]; d[++ d[0]] = x;
    fo(i, 2, d[0]) sum += sum_son[d[i]]; sum -= (d[0] - 1);
}

void dg2(int x) {
    if(x == t) return;
    bz[x] = 1;
    int mx = 0, mx2 = 0;
    for(int i = final[x]; i; i = next[i]) {
        int y = to[i]; if(bz[y]) continue;
        if(!bz_d[y]) w[y] = w[x] + sum_son[y];
        dg2(y);
        if(w[y] > mx) {
            mx2 = mx;
            mx = w[y];
        } else
        if(w[y] > mx2) {
            mx2 = w[y];
        }
    }
    w[x] = mx2 + sum_son[x];
    bz[x] = 0;
}

int pd(int m) {
    int yige = 0;
    fd(i, d[0], 2) {
        int dep = d[0] - i + 1;
        int ss = 0;
        int x = d[i];
        for(int j = final[x]; j; j = next[j]) {
            int y = to[j]; if(bz_d[y] || fa[y] != x) continue;
            if(w[y] + sum > m - yige) ss ++;
        }
        if(ss > dep - yige) return 0;
        yige += ss;
        if(m < yige) return 0;
        sum -= sum_son[x] - 1;
    }
    return 1;
}

int main() {
    freopen("compete.in", "r", stdin);
    freopen("compete.out", "w", stdout);
    scanf("%d %d %d", &n, &t, &s);
    fo(i, 1, n - 1) {
        scanf("%d %d", &x, &y);
        link(x, y);
    }
    dg(s);
    Build(t);
    dg2(s);
    int ans = 0;
    for(int l = 0, r = n; l <= r; ) {
        int m = (l + r) / 2;
        int sum2 = sum;
        if(pd(m)) ans = m, r = m - 1; else l = m + 1;
        sum = sum2;
    }
    printf("%d", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值