从零开始的DP学习系列之叁
树形DP的基本(常见?)思路:先递归进儿子,然后边回溯边决策,设状态时常设$dp[x]$表示以$x$为根的子树中(具体分析算不算$x$这个点)的情况
显然的二分答案,之后问题转化为用$m$个能覆盖$mid$范围的点能否覆盖所有的特殊点,用树形DP判断
设$unc[nde]$表示以$nde$为根(包含$nde$)的子树中最远的未被覆盖的特殊点的距离,$cov[nde]$以$nde$为根(包含$nde$)的子树中最近的选出的点的距离。有两个从儿子$goal[i]$获取信息的显然的转移
unc[nde]=max(unc[nde],unc[goal[i]]+1); cov[nde]=min(cov[nde],cov[goal[i]]+1);
然后考虑对点的选择,首先如果这个点是特殊点,而且$cov[nde]>mid$,说明这个点无法被子树中的点覆盖,只能交给父亲处理,于是更新一下它的$unc$的信息,告诉它的父亲考虑这个点
if(imp[nde]&&cov[nde]>mid) unc[nde]=max(unc[nde],0);
接下来考虑这个点的子树中的特殊点(们)能否靠这个点解决,如果$unc[nde]+cov[nde]<=mid$说明这个子树不需要父亲管了
if(unc[nde]+cov[nde]<=mid) unc[nde]=-inf;
最后是考虑是否选择这个点,这里贪心考虑,只在$unc$正好等于$mid$时选择这个点
if(unc[nde]==mid) unc[nde]=-inf,cov[nde]=0,tot++;
注意的是根节点没有父亲,要特殊考虑
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=300005,inf=0x3f3f3f3f; 6 int p[N],noww[2*N],goal[2*N]; 7 int imp[N],unc[N],cov[N]; 8 int n,m,t1,t2,cnt,tot,l,r,mid,ans; 9 void link(int f,int t) 10 { 11 noww[++cnt]=p[f]; 12 goal[cnt]=t,p[f]=cnt; 13 } 14 void DFS(int nde,int fth) 15 { 16 unc[nde]=-inf,cov[nde]=inf; 17 for(int i=p[nde];i;i=noww[i]) 18 if(goal[i]!=fth) 19 { 20 DFS(goal[i],nde); 21 unc[nde]=max(unc[nde],unc[goal[i]]+1); 22 cov[nde]=min(cov[nde],cov[goal[i]]+1); 23 } 24 if(imp[nde]&&cov[nde]>mid) unc[nde]=max(unc[nde],0); 25 if(unc[nde]+cov[nde]<=mid) unc[nde]=-inf; 26 if(unc[nde]==mid) unc[nde]=-inf,cov[nde]=0,tot++; 27 } 28 bool check(int x) 29 { 30 tot=0; DFS(1,0); 31 return tot+(unc[1]>=0)<=m; 32 } 33 int main () 34 { 35 scanf("%d%d",&n,&m),r=n; 36 for(int i=1;i<=n;i++) 37 scanf("%d",&imp[i]); 38 for(int i=1;i<n;i++) 39 { 40 scanf("%d%d",&t1,&t2); 41 link(t1,t2),link(t2,t1); 42 } 43 while(l<=r) 44 { 45 mid=(l+r)/2; 46 if(check(mid)) r=mid-1,ans=mid; 47 else l=mid+1; 48 } 49 printf("%d",ans); 50 return 0; 51 }