CEOI2017 D1T3 mousetrap 树形dp+二分答案

题意

一棵树,每条边上都没有标记,现在有一个人要从S走到T,每次你先选择一条有标记的边,并且给这条边的标记消除,或者把一个没标记的边打上永久标记(你无法再次修改使其消除),然后这个人会走随机一条标记为0的边(相当于回合制),并把这条边的标记设为1,问最坏情况下,你至少要改变多少次边的标记才可以使这个人从S走到T。

数据范围

n,s,t106

解法

这个题,有一个部分分是S与T有直接连边,我们用树形dp处理出这个人到每个点的最小代价,我们首先考虑这种情况,那么最坏情况下我们肯定是选择把除了T以外的,最小代价最大的路径封死,那么这时候这个人就会走代价第二大的路径,然后我们再进入这颗子树继续dp就好了。
那么对于S与T不直接相连的情况,我们就把这条S到T的链上的所有点都这样做一次,考虑二分答案,那么对于这条链上的每个支链,我们都要封死,所以我们记录一下到这个点已经花的代价,然后看这个点的代价加上已经用过的代价是否大于二分值,还有之前的步数是否足够我封锁完这些边,就可以了。

收获

这个题的突破口就是首先根据部分分得出树形dp的解,然后想办法延伸到通解上。
对于通解,二分答案是很难想到的东西,并且在check过程中有很多细节需要考虑。

考点:

树形dp 二分答案

易错点:

各种细节

#include <bits/stdc++.h>
#define ll long long
#define lf double
#define E complex<lf>
#define inf 0x7fffffff
#define eps 1e-8
#define pa pair<int,int>
#define pb push_back
#define ms(x,y) memset(x,y,sizeof(x))
#define l(x) (x<<1)
#define r(x) (x<<1|1)
#define mod 1000000007
#define N 1000010 
using namespace std;
inline int read() {
    int x=0,f=1;char c=getchar();
    while (c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
int n,s,t,du[N],f[N],fa[N];
int nxt[N<<1],head[N],to[N<<1],tot=0;
inline void link(int x,int y) {
    nxt[++tot]=head[x],head[x]=tot,to[tot]=y;
}
inline void dfs(int x) {
    for (int i=head[x]; i; i=nxt[i]) {
        int j=to[i];
        if (j==fa[x]) continue;
        fa[j]=x,dfs(j);
    }
    if (du[x]==1) f[x]=0;
    if (du[x]==2) f[x]=1;
    if (du[x]> 2) {
        int mx=-1,pos=-1,mx2=-1;
        for (int i=head[x]; i; i=nxt[i]) {
            int j=to[i];
            if (f[j]>mx&&j!=fa[x]) mx=f[j],pos=j;
        }
        for (int i=head[x]; i; i=nxt[i]) {
            int j=to[i];
            if (f[j]>mx2&&j!=pos&&j!=fa[x]) mx2=f[j];
        }
        f[x]=mx2+du[x]-2+1;
    }
}
int sta[N],top=0,sum[N];
inline bool check(int mid) {
    int tmp=0;
    for (int p=1; p<top; p++) {
        int x=sta[p],cnt=0;
        for (int i=head[x]; i; i=nxt[i]) {
            int j=to[i];
            if (j!=sta[p+1]&&j!=sta[p-1]&&sum[p]+f[j]+1-(p!=1)>mid-tmp) cnt++;  
        }
        tmp+=cnt;
        if (tmp>mid||tmp>p) return 0;
    }
    return 1;
}
int main() {
    freopen("angrybirds.in","r",stdin);
    freopen("angrybirds.out","w",stdout);
    n=read(),t=read(),s=read();
    for (int i=1; i<n; i++) {
        int x=read(),y=read();
        link(x,y),link(y,x),du[x]++,du[y]++;
    }
    dfs(t);
    for (int i=s; i!=t; i=fa[i]) sta[++top]=i;sta[++top]=t;
    for (int i=top-1; i; i--) sum[i]=sum[i+1]+du[sta[i]]-1-(sta[i]!=t);
    int l=0,r=n;
    while (l<r) {
        int mid=l+r>>1;
        if (check(mid)) r=mid; else l=mid+1;
    }
    cout << l << endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值