题意:小 A 成为了一个园艺家!他有一棵 n 个节点的树(如果你不知道树是什么,请
看 Hint 部分) 。他不小心打翻了墨水瓶,使得树的一些节点被染黑了。小 A 发
现这棵染黑了的树很漂亮,于是想从树中取出一个 x 个点的联通子图,使得这
些点中恰有 y 个黑点,他想知道他的愿望能否实现。可是他太小,不会算,请
你帮帮他。
结论:.对于某一大小的连通子图,其包含黑点数的最小值与最大值之间的所有点数目都能够取得到。
简单证明一下:考虑一个连通子图删除一个点再加入一个点后,黑点的数目变化最多只为1。因此可以变化到[min,max][min,max]之间所有的数目。
然后就可以使用树形背包dp。设f[i][j]f[i][j]表示从ii的子树中选出大小为jj的连通子图,黑点数目的最小值;g[i][j]g[i][j]表示黑点数目的最大值。然后树形背包转移即可。
注意树形背包的正确枚举姿势:只使用已经遍历过的点数目和当前子树中的点数目转移,否则会被链卡到O(n3)O(n3)。
时间复杂度O(Tn2)
#include<iostream> #include<cstdio> #include<cstring> #define maxn 5010 using namespace std; int n,m,head[maxn],num,v[maxn],sz[maxn],f[maxn][maxn],g[maxn][maxn]; struct node{int to,pre;}e[maxn*2]; void Insert(int from,int to){ e[++num].to=to; e[num].pre=head[from]; head[from]=num; } void dfs(int x,int father){ sz[x]=1;f[x][1]=g[x][1]=v[x]; for(int i=head[x];i;i=e[i].pre){ int to=e[i].to; if(to==father)continue; dfs(to,x); for(int j=sz[x];j>=1;j--) for(int k=sz[to];k>=1;k--){ f[x][j+k]=min(f[x][j+k],f[x][j]+f[to][k]); g[x][j+k]=max(g[x][j+k],g[x][j]+g[to][k]); } sz[x]+=sz[to]; } for(int i=1;i<=n;i++){ f[0][i]=min(f[0][i],f[x][i]); g[0][i]=max(g[0][i],g[x][i]); } } int main(){ int T;scanf("%d",&T); while(T--){ memset(head,0,sizeof(head));num=0; memset(f,0x3f,sizeof(f));memset(g,0xc0,sizeof(g)); scanf("%d%d",&n,&m); int x,y; for(int i=1;i<n;i++){ scanf("%d%d",&x,&y); Insert(x,y);Insert(y,x); } for(int i=1;i<=n;i++)scanf("%d",&v[i]); dfs(1,0); while(m--){ scanf("%d%d",&x,&y); if(y>=f[0][x]&&y<=g[0][x])puts("YES"); else puts("NO"); } puts(""); } return 0; }