2013编程之美挑战赛复赛---R2_B:招聘(01分数规划+DP)

总Time Limit: 
6000ms 
Memory Limit: 
262144kB
Description

Alice新开了一家公司,它的下面有两个项目,分别需要N1和N2个人来完成。现在有N个人前来应聘,于是Alice通过面试来决定他们中的哪些人会被录用。

Alice在面试中,会仔细考察他们能如何为公司的项目带来收益。她给每个人打了两个分值Q1和Q2,表示他加入第一个和第二项目分别能带来的收益值。同时,她也会仔细考察他们每个人的缺点,并且给每人打了另两个分值C1和C2,表示他们进入每个项目可能带来的负面效应。Alice心目中的最优决策是,在决定好录用哪些人以及每个人在哪个项目下工作之后,他们为公司带来的收益总和,除以他们为项目带来的负面效应总和,这个比值要最大。你能帮他计算出在最优决策下,这个比值为多少吗?

前来应聘的人数总是大于等于两个项目需求人数的总和,因此Alice一定会恰好招N1+N2个人,分配给第一个项目N1个人,分配给第二个项目N2个人,没有人会同时属于两个项目。


Input
输入文件包含多组测试数据。
第一行,给出一个整数T,为数据组数。接下来依次给出每组测试数据。
每组数据第一行为三个用空格隔开的整数N,N 1,N 2,表示前来应聘的人数,以及两个项目分别需要的人数。
接下来N行,每行是用空格隔开的四个整数Q 1,C 1,Q 2,C 2,依次表示每个人在第一个项目下的价值和负面效应,以及第二个项目下的价值和负面效应。

T ≤ 100
1 ≤ Q1, Q2 ≤ 2000
1 ≤ C1, C2 ≤ 50
小数据:0 < N 1 + N 2 ≤ N ≤ 50, 
大数据:0 < N 1 + N 2 ≤ N ≤ 500
Output
对于每组测试数据,输出一行"Case #X: Y",其中X表示测试数据编号,Y表示最优决策下招募的人的价值总和与负面效应总和的比值,与正确答案的绝对误差不应超过10 -6。所有数据按读入顺序从1开始编号。
Sample Input
1
5 2 2
12 5 8 3
9 4 9 4
7 3 16 6
11 5 7 5
18 10 6 3
Sample Output
Case #1: 2.444444

解题思路

这是一道典型的01分数规划问题,我们想要求这样一个函数的最大值 f(x)/g(x)(g(x)>0), 我们只需找到最小的r,使得存在x满足h(x)=f(x)-r*g(x)>=0,这个r即为解。而这个r可以通过二分来确定。我们现在只需在给定r的条件下,求解f(x)-r*g(x)的最大值即可。

                            

在这道问题中,f(x)=sum(Q1[i])+sum(Q2[j]) (i in S1, j in S2),g(x)=sum(C1[i])+sum(C2[j]) (i in S1, j in S2), 我们需要确定出两个集合S1,S2,使得f(x)-r*g(x)最大,即h(x)=sum(Q1[i]-r*C1[i])+sum(Q2[j]-r*C2[j])(i in S1, j in S2)。这里我们只需用动态规划来求解。令f[i][j][k]表示,在前i个人之中,有j个人属于S1,有k个人属于S2,此时最大的h(x)函数值为多少,转移很好考虑,即下一个人分在哪个项目或者不招进来。这样便能找到最大的h(x)。

 

仅仅做到这里还是不够,考虑到数据范围,这种做法可能会超时。一种优化思路是,当S1集合的人定下来时时,S2集合中的人一定可以贪心地选择剩余人中Q2[j]-r*C2[j]值最大的前N2个人,由此我们修改这个算法,先将所有人按Q2[j]-r*C2[j]从大到小排序,然后令f[i][j]表示前i个人之中,有j个人属于S2,并且不属于S1的人自动选前N2个人属于S2,此时h(x)的最大值。转移方式类似。如此一来,时间复杂度会下降一个阶,可以解决这道题目。

代码:
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<fstream>
#include<algorithm>
using namespace std;

typedef pair<double,double> PR;
const int MAXN=505;
int Q1[MAXN],C1[MAXN],Q2[MAXN],C2[MAXN];

//dp[i][j]:= 前i个人中,有j个人去第二个项目,此时最大的 h 
double dp[MAXN][MAXN];
PR h[MAXN]; 
int n,n1,n2;

bool Cmp(PR a,PR b){
	if(a.first!=b.first) return a.first>b.first;
	return a.second>b.second;
}
bool isok(double mid)
{
	for(int i=1;i<=n;i++){
		h[i].first=Q1[i]-mid*C1[i];
		h[i].second=Q2[i]-mid*C2[i];
	}
	sort(h+1,h+1+n,Cmp);
	
	double ans=0.0;
	dp[0][0]=0;
	
	for(int i=1;i<=n;i++){
		dp[i][0]=dp[i-1][0]+(i<=n1)*h[i].first;
		for(int j=1;j<i&&j<=n2;j++){
			dp[i][j]=max(dp[i-1][j]+(i-j<=n1)*h[i].first,dp[i-1][j-1]+h[i].second);
		} 
		if(i<=n2) dp[i][i]=dp[i-1][i-1]+h[i].second;
	}
	for(int i=n1+n2;i<=n;i++) ans=max(ans,dp[i][n2]);
	//cout<<ans<<endl;
	return ans>0;
}
void solve()
{
	double l=0,r=20000;
	while(r-l>0.000001) 
	{
		double mid=(l+r)/2;
		if(isok(mid)) l=mid;
		else r=mid;
	}
	printf("%.6f\n",(l+r)/2);
}
int main()
{
	int t,ca=1;
	cin>>t;
	while(t--)
	{
		cin>>n>>n1>>n2;
		for(int i=1;i<=n;i++){
			cin>>Q1[i]>>C1[i]>>Q2[i]>>C2[i];
		}
		cout<<"Case #"<<ca++<<": ";
		solve();
	}
	return 0;
}

可以去AC的地方:http://msbop.openjudge.cn/bop2013/R2_B/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值