题目大意:
输入一棵树,问是否存在⼀个子联通区域,大小为 S,有 B 个黑点 。多组询问,点数 N ≤ 5000
解法:
树形背包。
有个结论:如果一个大小为 S 联通块最多包含 L 个黑点,最多包含 R 个黑点,那么可以构造大小为 S ,包含 L~R 个黑点的联通块
然而并不知道这个结论哪里来的
于是设\(dp[x][s]\)表示以\(x\)为根,大小为\(s\)的子树最多/最少包含黑点数量,树形背包一下
时间复杂度\(O(n^2)\) 这我也不会证
代码:
#include <bits/stdc++.h>
#define N 5010
#define inf 99999999
using namespace std;
int head[N],mrk[N],f[N][N],g[N][N],l[N],r[N],cnt,sz[N];
struct ed{
int v,nxt;
}e[N<<1];
void add(int u,int v){
e[++cnt]=(ed){v,head[u]},head[u]=cnt;
e[++cnt]=(ed){u,head[v]},head[v]=cnt;
}
void dfs(int x,int fa){
f[x][1]=g[x][1]=mrk[x];
sz[x]=1;
for(int i=head[x];i;i=e[i].nxt){
int to=e[i].v;
if(to!=fa){
dfs(to,x);
for(int j=sz[x];j>=1;--j)
for(int k=sz[to];k>=1;--k){
f[x][j+k]=max(f[x][j]+f[to][k],f[x][j+k]);
g[x][j+k]=min(g[x][j]+g[to][k],g[x][j+k]);
}
sz[x]+=sz[to];
}
}
for(int i=1;i<=sz[x];++i){
l[i]=min(g[x][i],l[i]);
r[i]=max(f[x][i],r[i]);
}
}
void solve(){
int a,b,i,n,q,j;
scanf("%d%d",&n,&q);
for(i=1;i<=n;++i){
l[i]=inf,r[i]=-inf;
head[i]=mrk[i]=0;
for(j=1;j<=sz[i];++j)
f[i][j]=-inf,g[i][j]=inf;
sz[i]=0;
}
for(i=1;i<n;++i){
scanf("%d%d",&a,&b);
add(a,b);
}
for(i=1;i<=n;++i) scanf("%d",&mrk[i]);
dfs(1,0);
while(q--){
scanf("%d%d",&a,&b);
if(l[a]<=b && b<=r[a]) puts("Yes");
else puts("No");
}
cnt=0;
}
int main(){
int T;scanf("%d",&T);
memset(f,-1,sizeof(f));
memset(g,0x3f,sizeof(g));
while(T--) solve();
}