题目描述:
算法:
树形DP + 01背包
做法:
在树形DP的同时对每个点
u
维护一个背包 f[u][i],表示
u
这个子树内选 i 个叶子节点时,最多赚多少钱。那么有:
f[u][i]=maxj=1sizev(f[u][i−j]+f[v][j]−wij)
其中
v
是 u 的孩子,
sizev
代表
v
子树内叶子节点的个数, Wij 代表边
(i,j)
的长度。
思路表简单,但实现起来可能有点费劲。也可使用刷表法,更方便。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=3010, INF=1000000000;
int n, m, mi;
int head[N], f[N][N], pt[N], size[N];
struct Edge{
int to, next, w;
}e[N<<1];
inline void add(int u,int v,int w){
e[++mi] = (Edge){v,head[u],w};
head[u] = mi;
}
void dfs(int u){
for(int p=head[u], i, j, v, w; p; p=e[p].next){
v = e[p].to; dfs(v); w=e[p].w;
for(i=size[u]; i>=0; --i) for(j=size[v]; j>=0; --j){
f[u][i+j] = max(f[u][i+j], f[u][i]+f[v][j]-w);
}
size[u]+=size[v];
}
if(!head[u]) f[u][1]=pt[u], size[u]=1;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1, j; i<=n; ++i) for(j=1; j<=m; ++j) f[i][j]=-INF;
int tmp=n-m;
for(int i=1, j, k, v, c; i<=tmp; ++i){
scanf("%d",&k);
for(j=1; j<=k; ++j){
scanf("%d%d",&v,&c);
add(i,v,c);
}
}
for(int i=tmp+1, x; i<=n; ++i){ scanf("%d",&x); pt[i]=x; }
dfs(1); int i;
for(i=m; i>=0; --i) if(f[1][i]>=0) break;
printf("%d\n",i);
while(1);
}