bzoj 5072 小A的树 —— 树形DP

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=5072

由于对于一个子树,固定有 j 个黑点,连通块大小是一个连续的范围;

所以记 f[i][j] 表示以 i 为根的子树中选 j 个黑点,连通块最大的点数,g[i][j] 表示最小的点数;

然后普通树形DP即可,注意初始化;

但怎么处理询问?这道题卡空间,只能开 2.5 个 5000*5000 的 int 数组;

其实,对于整棵树,固定有 j 个黑点的连通块大小也是一个连续的范围,所以每个子树的答案直接贡献到 0 的答案上,就可以 O(1) 判断了。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int const xn=5005,inf=0x3f3f3f3f,base=75;
int T,n,q,f[xn][xn],g[xn][xn],hd[xn],ct,nxt[xn<<1],to[xn<<1],siz[xn],tf[xn],tg[xn];
bool b[xn];
void add(int x,int y){to[++ct]=y; nxt[ct]=hd[x]; hd[x]=ct;}
int rd()
{
  int ret=0,f=1; char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();}
  while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
  return f?ret:-ret;
}
void dfs(int x,int fa)
{
  siz[x]=b[x];
  if(b[x])f[x][1]=g[x][1]=1;//
  else f[x][0]=1,g[x][0]=1;//
  for(int i=hd[x],u;i;i=nxt[i])
    {
      if((u=to[i])==fa)continue;
      dfs(u,x); 
      memcpy(tf,f[x],sizeof f[x]);
      memcpy(tg,g[x],sizeof g[x]);
      for(int j=siz[x]+siz[u];j>=b[x];j--)
    for(int k=0;k<=min(j,siz[u]);k++)
      if(j-k<=siz[x])
        {
          f[x][j]=max(f[x][j],tf[j-k]+f[u][k]);
          g[x][j]=min(g[x][j],tg[j-k]+g[u][k]);
        }
      siz[x]+=siz[u];
    }
  if(!b[x])g[x][0]=0;
  else f[x][0]=g[x][0]=0;
  for(int j=0;j<=siz[x];j++)f[0][j]=max(f[0][j],f[x][j]),g[0][j]=min(g[0][j],g[x][j]);
}
int main()
{
  T=rd();
  while(T--)
    {
      n=rd(); q=rd(); ct=0;
      memset(hd,0,sizeof hd);
      memset(f,-2,sizeof f); memset(g,0x3f,sizeof g);
      for(int i=1,x,y;i<n;i++)
    {
      x=rd(); y=rd();
      add(x,y); add(y,x);
    }
      for(int i=1;i<=n;i++)b[i]=rd();
      dfs(1,0);
      for(int i=1,x,y;i<=q;i++)
    {
      x=rd(); y=rd();
      if(x>=g[0][y]&&x<=f[0][y])puts("YES");
      else puts("NO");
    }
      puts("");
    }
  return 0;
}
原文地址:https://www.cnblogs.com/Zinn/p/9818902.html