POJ 1155 树状DP

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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值