[Luogu P1273] 有线电视网

洛谷传送门

题目描述

某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。

从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。

现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。

写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。

输入输出格式

输入格式:

输入文件的第一行包含两个用空格隔开的整数 N N N M M M,其中 2 ≤ N ≤ 3000 2≤N≤3000 2N3000 1 ≤ M ≤ N − 1 1≤M≤N-1 1MN1 N N N为整个有线电视网的结点总数, M M M为用户终端的数量。

第一个转播站即树的根结点编号为 1 1 1,其他的转播站编号为 2 2 2 N − M N-M NM,用户终端编号为 N − M + 1 N-M+1 NM+1 N N N

接下来的 N − M N-M NM行每行表示—个转播站的数据,第 i + 1 i+1 i+1行表示第 i i i个转播站的数据,其格式如下:

K   A 1   C 1   A 2   C 2   …   A k   C k K\ A_1\ C_1\ A_2\ C_2\ …\ A_k\ C_k K A1 C1 A2 C2  Ak Ck

K K K表示该转播站下接 K K K个结点(转播站或用户),每个结点对应一对整数 A A A C C C A A A表示结点编号, C C C表示从当前转播站传输信号到结点 A A A的费用。最后一行依次表示所有用户为观看比赛而准备支付的钱数。

输出格式:

输出文件仅一行,包含一个整数,表示上述问题所要求的最大用户数。

输入输出样例

输入样例#1:
5 3
2 2 2 5 3
2 3 2 4 3
3 4 2
输出样例#1:
2

说明

样例解释

img

如图所示,共有五个结点。结点①为根结点,即现场直播站,②为一个中转站,③④⑤为用户端,共M个,编号从N-M+1到N,他们为观看比赛分别准备的钱数为3、4、2,从结点①可以传送信号到结点②,费用为2,也可以传送信号到结点⑤,费用为3(第二行数据所示),从结点②可以传输信号到结点③,费用为2。也可传输信号到结点④,费用为3(第三行数据所示),如果要让所有用户(③④⑤)都能看上比赛,则信号传输的总费用为2+3+2+3=10,大于用户愿意支付的总费用3+4+2=9,有线电视网就亏本了,而只让③④两个用户看比赛就不亏本了。

解题分析

稍加思索就可以发现这是一个树上的分组背包的问题。 但实现的时候发现似乎复杂度有点玄学…

不管这么多, 直接发代码吧…

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 3050
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    for (; !isdigit(c); c = gc);
    for (;  isdigit(c); c = gc)
    x = (x << 1) + (x << 3) + c - 48;
}
int n, m, cnt;
int head[MX], dp[MX][MX], val[MX], buf[MX];
struct Edge {int to, val, nex;} edge[MX << 1];
IN void add(R int from, R int to, R int val)
{edge[++cnt] = {to, val, head[from]}, head[from] = cnt;}
int DFS(R int now)
{
    if(now > n - m) return dp[now][1] = val[now], 1;
    int sum = 0, bf;
    R int i, j, k;
    for (i = head[now]; i; i = edge[i].nex)
    {
        if(edge[i].to)
        {
            bf = DFS(edge[i].to);
            for (j = 0; j <= sum; ++j) buf[j] = dp[now][j];
            for (j = 0; j <= sum; ++j)
            {
                for (k = 0; k <= bf; ++k)
                dp[now][j + k] = std::max(dp[now][j + k], buf[j] + dp[edge[i].to][k] - edge[i].val);
            }
            sum += bf;
        }
    }
    return sum;
}
int main(void)
{
    int siz, a, b;
    in(n), in(m);
    std::memset(dp, -63, sizeof(dp));//有负数权值的dp值
    for (R int i = 1; i <= n - m; ++i)
    {
        in(siz);
        for (R int j = 1; j <= siz; ++j)
        in(a), in(b), add(i, a, b);
    }
    for (R int i = 1; i <= m; ++i) in(val[n - m + i]);
    for (R int i = 1; i <= n; ++i) dp[i][0] = 0;
    DFS(1);
    int mx = 0;
    for (R int i = m; i; --i) if(dp[1][i] >= 0) return printf("%d", i), 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值