有线电视网(树上分组)

P1273 有线电视网

题目描述

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

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

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

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

输入格式

输入文件的第一行包含两个用空格隔开的整数 N N N M M M,其中 2 ≤ N ≤ 3000 2 \le N \le 3000 2N3000 1 ≤ M ≤ N − 1 1 \le M \le 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 \ \ \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 NM+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,有线电视网就亏本了,而只让 ③④ 两个用户看比赛就不亏本了。


模仿着带依赖的背包写的,有种说不出来的奇怪,可能是初始化是错的?

f [ u ] [ j ] f[u][j] f[u][j] 表示以 u u u 为根节点,花费 j j j 所能得到的最大用户的数量。

int n,m,son;
int h[3110],e[3110*2],ne[3110*2],idx,w[3110*2];
int money[3110];

int f[3110][31110];// f[u][j] 表示 u 为根的子树花费 j 取得的最大值

void add(int a,int b,int c){
    e[idx]=b;
    ne[idx]=h[a];
    w[idx]=c;
    h[a]=idx++;
}

void dfs(int u,int fa,int cost){
	bool flag = 0;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i],Cost=w[i];
        if(j==fa)continue;
     	debug(j);
     	debug(Cost);
        flag = 1;
        dfs(j,u,cost+Cost);
        for(int k=m;k>=cost;k--){
            for(int l=0;l<=k-cost;l++){
                f[u][k] = max(f[u][k],f[u][k-l]+f[j][l]);
            }
        }
    }
    if(!flag){//叶子节点
		for(int k=m;k>=cost;k--){
			if(k<=money[u]){
				f[u][k] = 1;
			}
		}
    }
}

void solve(){
    memset(h,-1,sizeof(h));
    cin>>n>>son;
    for(int i=1;i<=n-son;i++){
        int k;cin>>k;
        fo(j,1,k){
            int id,cost;cin>>id>>cost;
            cout<<i<<" "<<id<<" "<<cost<<endl;
            add(i,id,cost);
            add(id,i,cost);
            m+=cost;
        }
    }
    for(int i=n-son+1;i<=n;i++){
        int cost;cin>>cost;
        money[i]=cost;
    }
    dfs(1,-1,0);
    cout<<f[1][m]<<endl;
}

int main(){
    solve();
    return 0;
}

看了题解之后,人都不好了。

题解一般是定义成 f [ u ] [ j ] f[u][j] f[u][j] 表示以 u u u 为根节点,满足 j j j 个客户(叶子)所能获得的最大收益。

枚举 i i i ,找到最大的 f [ 1 ] [ i ] ≥ 0 f[1][i] \ge0 f[1][i]0

好有道理的样子

又TM陷入SB找不同了。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#include <assert.h>
#include <sstream>
#define pb push_back 
#define all(x) (x).begin(),(x).end()
#define mem(f, x) memset(f,x,sizeof(f)) 
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math,O3")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")

template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
template<typename T1,typename T2>
ostream& operator<<(ostream& os,const map<T1,T2>&v){for(auto c:v)os<<c.first<<" "<<c.second<<endl;return os;}
template<typename T>inline void rd(T &a) {
    char c = getchar(); T x = 0, f = 1; while (!isdigit(c)) {if (c == '-')f = -1; c = getchar();}
    while (isdigit(c)) {x = (x << 1) + (x << 3) + c - '0'; c = getchar();} a = f * x;
}

typedef pair<long long ,long long >PII;
typedef pair<long,long>PLL;

typedef long long ll;
typedef unsigned long long ull; 
const int N=2e5+10,M=1e9+7;

int n,son;
int h[3110],e[3110*2],ne[3110*2],idx,w[3110*2];
int money[3110];

int f[3110][3110];// f[u][j] 表示 u 为根的子树选取 j 个儿子盈利的最大值

void add(int a,int b,int c){
    e[idx]=b;
    ne[idx]=h[a];
    w[idx]=c;
    h[a]=idx++;
}

int dfs(int u,int fa){// 返回以u为根的子树中含有的叶子节点的个数
	if(u>n-son){
		f[u][1] = money[u];
		return 1;
	}
	int sum=0,t;
    for(int i=h[u];~i;i=ne[i]){
        if(e[i]==fa)continue;
		t = dfs(e[i],u);
        int cost = w[i];
        sum+=t;
        // cout<<endl;
        // debug(u);
        // debug(sum);
        // cout<<endl;
		/* 树上分组背包
		1:遍历组
		2:遍历体积
		3:遍历决策
		*/
		// cout<<u<<" "<<t<<endl;
		for(int j=sum;j>0;j--){
			for(int I=1;I<=t;I++){
				if(j>=I){
					cout<<cost<<endl;
					f[u][j]=max(f[u][j],f[u][j-I]+f[j][I]-cost);
					cout<<" "<<f[u][j]<<endl;
				}
			}
		}
		
    }
    	// cout<<u<<" "<<sum<<endl;
	return sum;
}

void solve(){
    memset(h,-1,sizeof(h));
    memset(f,~0x3f,sizeof f);
    cin>>n>>son;
    for(int i=1;i<=n-son;i++){
        int k;cin>>k;
        fo(j,1,k){
            int id,cost;cin>>id>>cost;
            add(i,id,cost);
            add(id,i,cost);
        }
    }
    for(int i=n-son+1;i<=n;i++){
        int cost;cin>>cost;
        money[i]=cost;
    }
    for(int i=1;i<=n;i++)f[i][0]=0;
    dfs(1,-1);
    for(int i=son;i>=0;i--){
    	debug(f[1][i]);
    	if(f[1][i]>=0){
    		cout<<i<<endl;
    		return ;
    	}
    }
}

int main(){
    solve();
    return 0;
}

破案了!

f[u][j]=max(f[u][j],f[u][j-I]+f[j][I]-cost);
应该是
f[u][j]=max(f[u][j],f[u][j-I]+f[e[i]][I]-cost);
变量多了,我就是猪脑子
int n,m;
int h[3110],e[3110*2],ne[3110*2],idx,w[3110*2];
int money[3110];

int f[3110][3110];// f[u][j] 表示 u 为根的子树选取 j 个儿子盈利的最大值

void add(int a,int b,int c){
    e[idx]=b;
    ne[idx]=h[a];
    w[idx]=c;
    h[a]=idx++;
}

int dfs(int u,int fa){// 返回以u为根的子树中含有的叶子节点的个数
	if(u>n-m){
		f[u][1] = money[u];
		return 1;
	}
	int sum=0,t;
    for(int i=h[u];~i;i=ne[i]){ // 遍历组
        if(e[i]==fa)continue;
		t = dfs(e[i],u);
        int cost = w[i];
        int next = e[i];
        sum+=t;
		/* 树上分组背包
		1:遍历组
		2:遍历体积
		3:遍历决策
		*/
		for(int j=sum;j>0;j--){ // 遍历体积,以u为根的子树的儿子节点的数目
			for(int i=1;i<=t;i++){ // 决策,这个分叉选多少个叶节点
				if(j>=i){
					f[u][j]=max(f[u][j],f[u][j-i]+f[next][i]-cost);
				}
			}
		}
		
    }
	return sum;
}

void solve(){
    memset(h,-1,sizeof(h));
    memset(f,~0x3f,sizeof f);
    cin>>n>>m;
    for(int i=1;i<=n-m;i++){
        int k;cin>>k;
        fo(j,1,k){
            int id,cost;cin>>id>>cost;
            add(i,id,cost);
        }
    }
    for(int i=n-m+1;i<=n;i++){
    	cin>>money[i];
    }
    for(int i=1;i<=n;i++)f[i][0]=0;
    dfs(1,-1);
    for(int i=m;i>=0;i--){
    	if(f[1][i]>=0){
    		cout<<i<<endl;
    		return ;
    	}
    }
}

int main(){
    solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值