poj1155
题意:给定一个树形图,节点数量3000。叶子节点是用户,每个用户如果能看上电视会交一定的电视费。看上电视的条件是从根到该用户的路径全部被修好,修每条边有一个费用。在不亏损(用户交钱总额>=修路总费用)的前提下,最多有多少人能看上电视。
分析:树形dp。dp[u][i][j]表示对于u节点,只看其前i个儿子对应的子树,在这些子树中总共让j个用户看上电视,这样的最大利润是多少(可以为负值)。
那么dp[u][i][j]=max(dp[u][i-1][j], dp[v][num[v][k] + dp[u][i - 1][j - k] - cost(u, v));其中v是u第i个儿子,num[v]表示以v为根的子树中总共有多少用户,cost(u,v)表示u到v这条边需要的花费。
以上是非叶子节点的转移方法,对于叶子节点则稍有不同,直接赋值为该用户交的钱即可。
#include <cstdio> #include <vector> using namespace std; #define D(x) const int INF = 0x3f3f3f3f; const int MAX_N = (int)(3e3) + 10; int n, m; int money[MAX_N]; vector<pair<int, int> > edge[MAX_N]; int dp[MAX_N][MAX_N]; int num[MAX_N]; int sum[MAX_N]; void input() { scanf("%d%d", &n, &m); for (int i = 1; i <= n - m; i++) { int k; scanf("%d", &k); for (int j = 0; j < k; j++) { int a, c; scanf("%d%d", &a, &c); edge[i].push_back(make_pair(a, c)); } } for (int i = n - m + 1; i <= n; i++) { scanf("%d", &money[i]); } } void dfs(int u, int cost) { if (u > n - m) { dp[u][0] = 0; num[u] = 1; sum[u] = money[u]; dp[u][1] = money[u]; return; } num[u] = 0; sum[u] = 0; for (int i = 0; i < (int)edge[u].size(); i++) { int v = edge[u][i].first; int w = edge[u][i].second; dfs(v, w); num[u] += num[v]; sum[u] += sum[v]; } fill_n(dp[u], num[u] + 1, -INF); dp[u][0] = 0; for (int i = 0; i < (int)edge[u].size(); i++) { int v = edge[u][i].first; int w = edge[u][i].second; for (int j = num[u]; j >= 1; j--) { int limit = min(j, num[v]); for (int k = 1; k <= limit; k++) { dp[u][j] = max(dp[u][j], dp[v][k] + dp[u][j - k] - w); } } } } int main() { input(); dfs(1, 0); int ans = 0; for (int i = 0; i <= num[1]; i++) { if (dp[1][i] >= 0) ans = max(ans, i); } for (int i = 1; i <= n; i++) { for (int j = 0; j <= num[i]; j++) { D(printf("dp[%d][%d]=%d\t", i, j, dp[i][j])); } D(puts("")); } printf("%d\n", ans); return 0; }