题目: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; }