题解
dp[u][j]
表示u
的子树中选了j
个终端的盈利。这里设v
为u
的子树。
状态转移:
dp[u][j]=max{dp[v][k+t]-cost}
,和分组背包类似,把子树v
看成一个组,然后从子树v
中选t
个物品。
这里要注意的是树形dp其实自动省略了一维i
,本质上是dp[i][u][j]
,前i
颗子树中第u
个结点有j
个物品的盈利。这里用dfs
从下往上更新可以省略掉第一维,所以这里对状态更新的时候需要逆序枚举j
,原理跟一维01背包一样。
https://www.luogu.org/problemnew/show/P1273
#include <bits/stdc++.h>
using namespace std;
#define FOR0(a,b) for(int i = a; i < b; ++i)
#define FORE(a,b) for(int i = a; i <= b; ++i)
typedef long long ll;
typedef pair<int,int> pii;
const int INF = 0x3f3f3f3f;
const int maxn = 3000+5;
int n,m;
vector<pii> G[maxn];
int cost[maxn], dp[maxn][maxn], sum[maxn];
int tmp[maxn];
void add(int u, int v, int w) {
G[u].push_back(make_pair(v,w));
}
void dfs(int u, int fa) {
if(u > n-m) sum[u] = 1;
for(int i = 0; i < G[u].size(); ++i) {
int v = G[u][i].first;
if(v == fa) continue;
dfs(v,u);
sum[u] += sum[v];
}
if(u > n-m)
dp[u][1] = cost[u];
else {
int t = 0;
for(int i = 0; i < G[u].size(); ++i) {
int v = G[u][i].first;
if(v == fa) continue;
t += sum[v];
//for(int j = 0; j <= t; ++j) tmp[j] = dp[u][j];
for(int j = t; j >= 0; --j) {
for(int k = 0; k <= j; ++k) {
dp[u][j] = max(dp[u][j],dp[u][j-k]+dp[v][k]-G[u][i].second);
}
// cout << u <<" " << j <<" " << dp[u][j] << endl;
}
}
}
}
int main() {
memset(dp, -INF, sizeof dp);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) dp[i][0] = 0;
for(int i = 1; i <= n-m; ++i) {
int k;
scanf("%d", &k);
int v,w;
for(int j = 0; j < k; ++j) {
scanf("%d%d", &v, &w);
add(i,v,w);
}
}
for(int j = n-m+1; j <= n; ++j)
scanf("%d", &cost[j]);
dfs(1,-1);
int j;
for(j = m; j >= 0; --j) {
if(dp[1][j] >= 0) break;
}
cout << j << endl;
return 0;
}