bzoj 3420: Poi2013 Triumphal arch 树形dp+二分

给一颗树,$1$ 号节点已经被染黑,其余是白的,两个人轮流操作,一开始 $B$ 在 $1$ 号节点,$A$ 选择 $k$ 个点染黑,然后 $B$ 走一步,如果 $B$ 能走到 $A$ 没染的节点则 $B$ 胜,否则当 $A$ 染完全部的点时,$A$ 胜。求能让 $A$ 获胜的最小的 $k$

 

我们发现这个 $k$ 是满足单调性的:即如果 $k$ 是一个合法的解,那么 $k+1$ 也一定合法,所以考虑二分 $k$

现在,我们考虑如果得到一个 $mid$,如何验证 $mid$ 是否合法呢 $?$

这个状态的设计不太好想:$f[i]$ 表示当 $B$ 到达 $i$ 节点且 $B$ 为后手时 $i$ 的子树中需要被染成黑色的最小数量(不考虑 $i$ 的染色情况).

如果 $f[1]$ 小于等于 $k$ 的话,则合法.

考虑如何转移:

对于 $i$ 的所有子树来说,答案是 $\sum_{v\in son[i]}f[v]+1$

因为我们并不考虑一个节点自身的染色情况,所以所有儿子也必须都被提前染上色.

而由于 $B$ 是后手,$A$ 是先手,所以 $A$ 可以提前将 $k$ 个点先染完,所以:

$f[i]=max(0,\sum_{v\in son[i]}(f[v]+1)-k)$

每一次二分出一个 $k$ 就这么转移一下就好了.

#include <bits/stdc++.h>      
#define N 300005   
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std;  
int n,edges,k;        
int hd[N],to[N<<1],nex[N<<1],f[N];        
void add(int u,int v) 
{
    nex[++edges]=hd[u],hd[u]=edges,to[edges]=v;   
}
void dfs(int u,int ff) 
{
    int tot=0;      
    f[u]=-k;            
    for(int i=hd[u];i;i=nex[i]) 
    {
        int v=to[i]; 
        if(v==ff) continue;   
        dfs(v,u);   
        f[u]+=f[v]+1;   
    }   
    // printf("%d %d\n",u,f[u]);    
    f[u]=max(f[u], 0);   
}
int main() 
{ 
    // setIO("input"); 
    int i,j; 
    scanf("%d",&n);  
    for(i=1;i<n;++i) 
    {
        int u,v; 
        scanf("%d%d",&u,&v),add(u,v),add(v,u);   
    }
    int l=0,r=n,ans=n;   
    while(l<=r) 
    {
        int mid=(l+r)>>1;     
        for(i=1;i<=n;++i)  f[i]=0;   
        k=mid, dfs(1,0);    
        if(f[1]==0)   ans=mid, r=mid-1;  
        else    l=mid+1; 
    }
    printf("%d\n",ans);    
    return 0;   
}

  

posted @ 2019-10-31 00:24  EM-LGH  阅读(151)  评论(0编辑  收藏  举报