做法就是每次将一个点的所有子节点的答案合并到这个点上去,暴力的合并可以每次枚举子节点背包的大小,这样的复杂度是 O(ns2) O ( n s 2 ) 的。可以利用启发式合并的trick,每次将子节点中最重的儿子直接复制过来,然后对于其他子树中的所有节点直接暴力合并,由于合并单个节点的复杂度是 O(s) O ( s ) 的,而每次合并了以后子树大小至少变成了原来的2倍,所以每个节点最多被合并 log2n l o g 2 n 次,这样,复杂度就是 O(nslogn) O ( n s l o g n )
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 5006
#define all 5000
#define LL long long
using namespace std;
int n,T,tot[2],num[maxn],w[maxn],p[maxn],lnk[2][maxn],son[2][maxn*2],nxt[2][maxn*2];
LL f[maxn][maxn];
bool vis[maxn];
void add(int t,int x,int y){
nxt[t][++tot[t]]=lnk[t][x];son[t][tot[t]]=y;lnk[t][x]=tot[t];
}
void dfs(int x){
vis[x]=0;num[x]=1;
for(int j=lnk[0][x];j;j=nxt[0][j]) if(vis[son[0][j]]){
add(1,x,son[0][j]);dfs(son[0][j]);num[x]+=num[son[0][j]];
}
}
void merge(int t,int x){
for(int j=all;j>=w[x];j--)f[t][j]=max(f[t][j],f[t][j-w[x]]+p[x]);
for(int j=lnk[1][x];j;j=nxt[1][j])merge(t,son[1][j]);
}
void solve(int x){
int Max=0,t=0;vis[x]=0;
for(int j=lnk[1][x];j;j=nxt[1][j]){
if(num[son[1][j]]>Max)Max=num[son[1][j]],t=son[1][j];
solve(son[1][j]);
}
if(t)memcpy(f[x],f[t],sizeof(f[x]));
for(int j=all;j>=w[x];j--)f[x][j]=max(f[x][j],f[x][j-w[x]]+p[x]);
for(int j=lnk[1][x];j;j=nxt[1][j]) if(son[1][j]!=t)merge(x,son[1][j]);
}
int main(){
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
scanf("%d",&n);
for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),add(0,x,y),add(0,y,x);
for(int i=1;i<=n;i++)scanf("%d%d",&p[i],&w[i]);
memset(vis,1,sizeof(vis));
dfs(1);solve(1);
scanf("%d",&T);
while(T--){
int x,s;
scanf("%d%d",&x,&s);
printf("%lld\n",f[x][s]);
}
return 0;
}