题目:https://www.luogu.org/problemnew/show/P1273
题意:一棵树,叶子节点是用户,每天边有一个权值表示花费,每一个用户有一个值表示他们会交的钱。
问在不亏本的情况下,最多可以选择多少个用户,让他们得到从根节点(1)发送出的服务。
思路:本来很天真的以为是先dfs处理出每个叶子节点到根的净利润,然后背包。【太傻逼了】
但是同一棵子树上的节点共用了一段路径,这里是不用重复算的。
所以要树形dp,$dp[i][j]$表示以$i$为根的子树上选了$j$个节点。
$dp[i][j] = max(dp[i][j], dp[i][j-k]+dp[son][k]-e.w)$
$j$的范围是$i$的子孙数,这个需要dfs的时候统计,$k$的范围是$son$这棵子树的大小。
#include<cstdio>
#include<cstdlib>
#include<map>
#include<set>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<stack>
#include<queue>
#include<iostream> #define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
typedef pair<int, int> pr; int n, m;
const int maxn = ;
int head[maxn], tot;
struct edge{
int to, w, nxt;
}e[maxn];
int pay[maxn];
int dp[maxn][maxn]; void add(int x, int y, int c)
{
e[++tot].to = y;
e[tot].w = c;
e[tot].nxt = head[x];
head[x] = tot;
} int dfs(int now)
{
if(now > n - m){
dp[now][] = pay[now];
return ;
}
int sum = , son = ;
for(int i = head[now]; i; i = e[i].nxt){
int to = e[i].to;
//printf("%d %d\n", now, e[i].to);
son = dfs(to);
sum += son;
for(int j = sum; j > ; j--){
for(int k = ; k <= son; k++){
if(j >= k)dp[now][j] = max(dp[now][j], dp[now][j - k] + dp[to][k] - e[i].w);
}
}
}
return sum;
} int main()
{
memset(dp, ~0x3f, sizeof(dp));
memset(head, , sizeof(head));
scanf("%d%d", &n, &m);
for(int i = ; i <= n - m; i++){
int k;
scanf("%d", &k);
while(k--){
int a, c;
scanf("%d%d", &a, &c);
add(i, a, c);
}
dp[i][] = ;
}
for(int i = n - m + ; i <= n; i++){
scanf("%d", &pay[i]);
dp[i][] = ;
}
dfs();
for(int i = m; i > ; i--){
if(dp[][i] >= ){
printf("%d\n", i);
break;
}
}
}