OJ:http://poj.org/problem?id=1155
简单翻译:电视台转播节目。对于每个节点,如果其存在子节点,其子结点可能是用户,也可能是中转站。但是用户肯定是叶子结点。传到中转站或是用户都要花钱,如果是用户,则收钱。问在不亏本的前提下最多能有多少个用户看到节目。
一开始看到这个题目想法完全错误,以为是简单DP问题,理解错了,想象成了求最大利润,后来发现并不是这么简单。
说实话,这个题有点麻烦
这里要求的是保证不亏本的情况下,最多有多少个用户能看到比赛,其实最终决定的也就是选择给哪些用户传递信号。
最重要的其实是用户,而不是传播路上的节点,DP的时候不应该去选择路径上的节点,而是去选择用户,也就是叶子,感觉这句话说起来有点奇怪。其实这也就是树状DP的核心思想,也是于普通DP的最大区别
DP方程:dp[index][j] = max(dp[index][j],dp[index][j-k]+dp[next][k]-map[index].w[i]);
index表示当前节点,j表示给当前节点下的j个用户传递信号。
next表示其子节点,map[index].w[i]表示当前节点到其子节点所需要的花费
还有一个小的注意点:权值的花费都在路径上,但是收钱的用户节点的值,是在点上。要进行特殊的处理,不过好在用户节点全部都是子节点。
我们用dp[i][1]来表示当前用户节点收到的钱。把其也想象成一个中转站,用户节点在其下一个位置,也就相当于把点上的值,转换到了路径上
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 3001
#define max(x,y) x > y ? x : y;
typedef struct{
// 子节点
int next[MAX];
// 到达子节点路径上的权值
int w[MAX];
// 当前节点的子节点数量
int size;
}Node;
int dfs(int index);
Node map[MAX];
// dp[i][j]表示节点i,给j个用户输出信号的利润值
int dp[MAX][MAX];
int money[MAX];
int n,m;
int main()
{
int k,i,j,w,to;
scanf("%d%d",&n,&m);
// 因为dp过程中的值会小于0,所以这里应该初始化为一个负值
memset(dp,-0x3f,sizeof(dp));
for(int i=1;i<=n;i++){
map[i].size = 0;
// 一个节点不给其用户节点传递信号,那么他当前的值肯定是0
dp[i][0] = 0;
}
for(i=1;i<=n-m;i++){
scanf("%d",&k);
for(j=0;j<k;j++){
scanf("%d%d",&to,&w);
// 向队列中放
map[i].next[map[i].size] = to;
map[i].w[map[i].size++] = w;
}
}
// 只有用户节点采用收入值
for(i=n-m+1;i<=n;i++){
scanf("%d",&money[i]);
}
dfs(1);
for(i=m;i>0;i--){
// 从观众最多的开始,如果不亏本,得到结果
if(dp[1][i]>=0){
printf("%d\n",i);
break;
}
}
return 0;
}
int dfs(int index){
int i,j,k;
// 判断当前节点是不是观众节点
if(index > n - m){
dp[index][1] = money[index];
return 1;
}
int sum = 0;
for(int i=0;i<map[index].size;i++){
// 子节点
int next = map[index].next[i];
// s:子节点的观众数量
int s = dfs(next);
sum += s;
for(j=sum;j>0;j--){
for(k=1;k<=s;k++){
if(j-k>=0){
dp[index][j] = max(dp[index][j],dp[index][j-k]+dp[next][k]-map[index].w[i]);
}
}
}
}
return sum;
}