Poj 1155 TELE (DP_树形DP(背包))

题目链接:http://poj.org/problem?id=1155


题目大意:给定一棵树,1为根结点表示电视台,有m个叶子节点表示客户,有n-m-1个中间节点表示中转站,每条树边有权值。现在要在电视台播放一场比赛,每个客户愿意花费cost[i]的钱观看,而从电视台到每个客户也都有个费用,并且经过一条边只会产生一个费用。问电视台不亏损的情况最多有几个客户可以看到比赛?1<=n<=1000,1<=m<=n-1;


解题思路:本题解法为树形DP+背包,和Poj1947有点相似,1947也是在树形结构上进行分组背包处理。本题在思考的时候可分两种情况:1、当节点为叶子节点时,每个用户都要支付Money,那当前选一个的价值为Money值,中转站和电视台要计算的是在保证不亏损也就是从用户那获得得价值减去中转费用必须大等于0,那么叶子节点的Money值就是基本信息。2、当节点为中转站或者非叶子节点时,每次从子节点中选择n个并计算收取的Money和中转费用,如果两者想减大等于0就用n来更新答案。怎么转换到分组背包呢?可以这样想,每次从每个子节点中都只能取若干个,不可能重叠着取,那几个节点就是几组背包,最大容量是这点的叶子子孙数量,选几个节点就是选择的容量,价值就是用户给的Money-中转费用。

    假设dp[i][j]表示以i为根节点选择j个用户产生的最大价值。

    状态转移方程: dp[v][1] = Money[v]; (v为叶子节点)

                          dp[v][j] = max(dp[v][j],dp[v][j-i] + dp[k][i] - len);(v为非叶子节点,j表示用户个数,i为容量,k为v的子节点,len为边权)

    算法复杂度O(sum(num[i],num[s])) (num[i]为某个节点的叶子子孙个数,num[s]为i的子节点的叶子子孙个数)


测试数据:

6 5
5 2 3 3 4 4 5 5 6 6 7
1 1 1 1 1

9 6
3 2 2 3 2 9 3
2 4 2 5 2
3 6 2 7 2 8 2
4 3 3 3 1 1


代码:

#include <stdio.h>
#include <string.h>
#define MAX 3010
#define INF 0xfffffff
#define max(a,b) (a)>(b)?(a):(b)


struct node {

    int v,len;
    node *next;
}*head[MAX*3],tree[MAX*3];
int ans,sum[MAX],vis[MAX];
int n,m,ptr,dp[MAX][MAX],money[MAX];


void AddEdge(int x,int y,int len) {

    tree[ptr].v = y,tree[ptr].len = len;
    tree[ptr].next = head[x],head[x] = &tree[ptr++];
    tree[ptr].v = x,tree[ptr].len = len;
    tree[ptr].next = head[y],head[y] = &tree[ptr++];
}
void Dfs(int v,int len) {

    if (vis[v]) return ;
    vis[v] = 1,dp[v][0] = 0;


    int i,j,k,tot = 0;
    node *p = head[v];


    while (p != NULL) {

        if (!vis[p->v]) {

            tot++;
            Dfs(p->v,len);
            sum[v] += sum[p->v];
        }
        p = p->next;
    }


    if (tot == 0) {
    //根节点
        sum[v] = 1;
        dp[v][1] = money[v] - len;
    }
    else {

        p = head[v];
        while (p != NULL) {

            k = p->v;
            len = p->len;
            for (j = sum[v]; j >= 1; --j)
                for (i = 1; i <= sum[k]; ++i)
                    if (j >= i && dp[v][j-i] != -INF && dp[k][i] != -INF)
                        dp[v][j] = max(dp[v][j],dp[v][j-i] + dp[k][i] - len);
            p = p->next;
        }
    }
}


int main()
{
    int i,j,k,a,b;
    

    while (scanf("%d%d",&n,&m) != EOF) {

        ptr = 1,ans = -INF;
        memset(sum,0,sizeof(sum));
        memset(vis,0,sizeof(vis));
        memset(head,NULL,sizeof(head));
        for (i = 0; i <= n; ++i)
            for (j = 0; j <= n; ++j)
                dp[i][j] = -INF;


        for (i = 1; i <= n - m; ++i) {

            scanf("%d",&k);
            for (j = 1; j <= k; ++j)
                scanf("%d%d",&a,&b),AddEdge(i,a,b);
        }
        for (i = n - m + 1; i <= n; ++i)
            scanf("%d",&money[i]);


        Dfs(1,0);
        for (i = n; i >= 0; --i)
            if (dp[1][i] >= 0) {
                
                printf("%d\n",i);
                break;
            }
        
    }
    return 0;
}


本文ZeroClock原创,但可以转载,因为我们是兄弟。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值