P1273 有线电视网
题目描述
某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。
从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。
现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。
写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。
输入格式
输入文件的第一行包含两个用空格隔开的整数 N N N 和 M M M,其中 2 ≤ N ≤ 3000 2 \le N \le 3000 2≤N≤3000, 1 ≤ M ≤ N − 1 1 \le M \le N-1 1≤M≤N−1, N N N 为整个有线电视网的结点总数, M M M 为用户终端的数量。
第一个转播站即树的根结点编号为 1 1 1,其他的转播站编号为 2 2 2 到 N − M N-M N−M,用户终端编号为 N − M + 1 N-M+1 N−M+1 到 N N N。
接下来的 N − M N-M N−M 行每行表示—个转播站的数据,第 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 \ \ \ldots \ \ 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 的费用。最后一行依次表示所有用户为观看比赛而准备支付的钱数。单次传输成本和用户愿意交的费用均不超过 10。
输出格式
输出文件仅一行,包含一个整数,表示上述问题所要求的最大用户数。
输入输出样例 #1
输入 #1
5 3
2 2 2 5 3
2 3 2 4 3
3 4 2
输出 #1
2
说明/提示
样例解释
如图所示,共有五个结点。结点 ① 为根结点,即现场直播站,② 为一个中转站,③④⑤ 为用户端,共 M M M 个,编号从 N − M + 1 N-M+1 N−M+1 到 N N N,他们为观看比赛分别准备的钱数为 3 3 3、 4 4 4、 2 2 2。
从结点 ① 可以传送信号到结点 ②,费用为 2 2 2;
也可以传送信号到结点 ⑤,费用为 3 3 3(第二行数据所示);
从结点 ② 可以传输信号到结点 ③,费用为 2 2 2;
也可传输信号到结点 ④,费用为 3 3 3(第三行数据所示)。
如果要让所有用户(③④⑤)都能看上比赛,则信号传输的总费用为: 2 + 3 + 2 + 3 = 10 2+3+2+3=10 2+3+2+3=10,大于用户愿意支付的总费用 3 + 4 + 2 = 9 3+4+2=9 3+4+2=9,有线电视网就亏本了,而只让 ③④ 两个用户看比赛就不亏本了。
思路:这个有线电视网是树状背包的板子题,作为树状背包,我们应该要对这个树进行倒序排序,这样子我们在枚举所谓每个节点(也就是商品的时候),是严格遵守从下到上的顺序进行的,此外还有就是统计以当前节点为根节点的子树的大小,因为我们发现有线电视在传输的过程中不能像正常背包一样,只选择子树的一部分,要么选择整个子树,要么就不选,所以我们需要统计子树大小的信息,以方便我们进行状态转移。
此外,还有一个在进行倒序标记的过程中,我们需要重新进行编号
还有在进行背包的过程中,我们对于背包的状态有二维
一维是树节点的编号,二维是背包容量,而对于背包容量为零也就是j==0时,我们初始化为0,而根据题目要求,对于任意一个合法的方案,只要他最终不会使广播电视台的收入为负即可,所以剩余的j>0的部分我们应该赋值为较大的负数。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=3e3+11;
int n,m;
int k;
int cnt,he[N],nxt[N],to[N],val[N];
int a,c;
int ddp[N],sz[N],idx[N];
int tot;
int dp[N][N];
int ans;
void add(int u,int v,int val)
{
++cnt;
nxt[cnt]=he[u];
he[u]=cnt;
to[cnt]=v;
}
void dfs(int now)
{
sz[now]=1;
for(int i=he[now];i;i=nxt[i])
{
int tar=to[i];
dfs(tar);
sz[now]+=sz[tar];
}
idx[++tot]=now;//重新编号
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n-m;++i)
{
scanf("%d",&k);
for(int j=1;j<=k;++j)
{
scanf("%d%d",&a,&c);
add(i,a,c);
ddp[a]-=c;
}
}
dfs(1);
for(int i=n-m+1;i<=n;++i)
{
int tmp1;
scanf("%d",&tmp1);
ddp[i]+=tmp1;
}
for(int i=0;i<=tot;++i)
for(int j=1;j<=m;++j)
dp[i][j]=-0x3f3f3f3f;
for(int i=1;i<=tot;++i)//重新编号,编号越小代表越是叶子结点
{
int now=idx[i];
for(int j=1;j<=m;++j)//j表示背包容量,而m为客户最大数量,所以最大容量枚举到m
{
if(n-m+1<=now)//如果n-m+1<=u就代表此时u在叶子结点
dp[i][j]=max(dp[i-1][j-1]+ddp[now],dp[i-1][j]);//要么带上,要么不带
else
dp[i][j]=max(dp[i-1][j]+ddp[now],dp[i-sz[now]][j]);//要么放弃子树上的一切
//要么接着
}
}
for(int i=1;i<=m;++i)
if(dp[tot][i]>=0)
ans=i;
printf("%d\n",ans);
return 0;
}