[oiclass1461]有线电视网:树上背包

题目

某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。
从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。
现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。
写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。

输入

输入文件的第一行包含两个用空格隔开的整数 \(N\)\(M\),其中 \(2≤N≤3000,1≤M≤N-1\)\(N\)为整个有线电视网的结点总数,\(M\)为用户终端的数量。
第一个转播站即树的根结点编号为 \(1\),其他的转播站编号为 \(2\)\(N-M\),用户终端编号为 \(N-M+1\)\(N\)
接下来的 \(N-M\) 行每行表示—个转播站的数据,第 \(i+1\) 行表示第 \(i\) 个转播站的数据,其格式如下:
\(K\ A_1\ C_1\ A_2\ C_2\ \cdots \ A_k\ C_k\)
\(K\)表示该转播站下接 \(K\) 个结点(转播站或用户),每个结点对应一对整数 \(A\)\(C\)\(A\) 表示结点编号,\(C\) 表示从当前转播站传输信号到结点 \(A\) 的费用。
最后一行依次表示所有用户为观看比赛而准备支付的钱数。

输出

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

输入样例

5 3
2 2 2 5 3
2 3 2 4 3
3 4 2

输出样例

2

样例解释

image

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

题解

树上背包
定义f[i][j]表示以i为根的子树满足j个用户观看球赛的盈利情况。结果为求出f[i][j]>=0情况下的最大j。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=3000+5;
struct edge{
	int u,v,w;
};
vector<edge> g[N];
int n,m, val[N],f[N][N],k,a,c,size[N];
void dfs(int u){
	f[u][0]=0; //边界,初始值 
	if(u>n-m){ //叶子结点,特殊处理 
		f[u][1]=val[u];
		size[u]=1;
		return;	
	}
	for(int i=0;i<g[u].size();i++){  
		int v=g[u][i].v;
		int w=g[u][i].w;
		dfs(v);
		size[u]+=size[v];//统计该子树下的用户数 
		for(int j=m;j>=0;j--){ //树上背包 
			for(int k=0;k<=min(j,size[v]);k++){ //如果v子树下最多有size[v]个用户,所以k不能大于size[v] 
				f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]-w);//选择v的子树,需要减去传输费用w 
			}
		}
	}
}
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);
			g[i].push_back((edge){i,a,c});
		}
	}
	for(int i=n-m+1;i<=n;i++){
		scanf("%d",&val[i]);
	}
	memset(f,~0x3f,sizeof(f));//初始化为极小值,因为会有亏损情况(负数的情况) 
	dfs(1);
	for(int i=m;i>=0;i--){//求出在不亏损的情况能满足的最大用户数 
		if(f[1][i]>=0){
			printf("%d",i);
			return 0;
		}
	}
}
posted @ 2022-01-12 10:33  chxulong  阅读(63)  评论(0编辑  收藏  举报