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; }