树上背包
简介
树上背包,顾名思义,就是在树上做背包。
比如这道题:
收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。
从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。
现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。
写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。
定义 \(dp_{u,i}\) 表示在节点 \(u\) 的子数内,选了 \(i\) 个用户的最大价值(注意这里压了一维)。而转移就是 \(dp_{u,i+j}=dp_{u,i}+dp_{v,j}+w\),这里 \(u\) 是 \(v\) 的父亲。
代码
#include<bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;
const int MAXN = 3001, INF = 30001;
int n, m, a[2 * MAXN], dp[2 * MAXN][MAXN], sz[2 * MAXN];
vector<pii> e[MAXN];
void dfs(int u, int fa) {
if(u > n) {
dp[u][0] = 0, dp[u][1] = a[u], sz[u] = 1;
return;
}
for(auto [v, w] : e[u]) {
if(v != fa) {
dfs(v, u);
sz[u] += sz[v];
for(int i = sz[u]; i >= 0; --i) {
for(int j = 0; j <= min(i, sz[v]); ++j) {
dp[u][i] = max(dp[u][i], dp[u][i - j] + dp[v][j] - w);
}
}
}
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
n -= m;
for(int i = 1, k; i <= n; ++i) {
cin >> k;
for(int j = 1, v, w; j <= k; ++j) {
cin >> v >> w;
e[i].push_back({v, w});
}
}
for(int i = 1; i <= m; ++i) {
cin >> a[n + i];
}
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
dp[i][j] = -INF;
}
}
dfs(1, 0);
for(int i = m; i >= 0; --i) {
if(dp[1][i] >= 0) {
cout << i;
return 0;
}
}
return 0;
}