2013编程之美挑战赛复赛第二轮---招聘

本博客探讨了在招聘场景中如何运用01分数规划解决最优决策问题。Alice面临招聘任务,需要找到使收益与负面影响之比最大的员工分配方案。通过二分法和动态规划,可以求解最大比值并确保算法效率。样例输入和解题思路展示了具体的应用过程。
摘要由CSDN通过智能技术生成

题目2:招聘

时间限制: 3000ms 内存限制: 256MB

描述

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

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

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

输入

输入文件包含多组测试数据。

第一行,给出一个整数T,为数据组数。接下来依次给出每组测试数据。

每组数据第一行为三个用空格隔开的整数NN1N2,表示前来应聘的人数,以及两个项目分别需要的人数。

接下来N行,每行是用空格隔开的四个整数Q1C1Q2C2,依次表示每个人在第一个项目下的价值和负面效应,以及第二个项目下的价值和负面效应。

输出

对于每组测试数据,输出一行"Case #X: Y",其中X表示测试数据编号,Y表示最优决策下招募的人的价值总和与负面效应总和的比值,与正确答案的绝对误差不应超过10-6。所有数据按读入顺序从1开始编号。

数据范围

T ≤ 100

1 ≤ Q1, Q2 ≤ 2000

1 ≤ C1, C2 ≤ 50

小数据:0 < N1 + N2 ≤ N ≤ 50,

大数据:0 < N1 + N2 ≤ N ≤ 500

 

样例输入

1

5 2 2

12 5 8 3

9 4 9 4

7 3 16 6

11 5 7 5

18 10 6 3

样例输出

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)的最大值。转移方式类似。如此一来,时间复杂度会下降一个阶,可以解决这道题目。

代码参考排名第二的zzz:学习一下,不愧为微软。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <map>
#include <string>
#include <cstring>
#include <sstream>
#include <algorithm>

using namespace std;

const int MAXN = 505;

int n, n1, n2;
int q1[MAXN], c1[MAXN], q2[MAXN], c2[MAXN];
pair<double, double> a[MAXN];
double f[MAXN][MAXN];
bool ok(double ans)
{
    for (int i = 1; i <= n; ++i) {
		a[i] = make_pair(q1[i] - ans * c1[i], q2[i] - ans * c2[i]);
	}
	sort(a + 1, a + n + 1);
	reverse(a + 1, a + n + 1);
	double res = 0;
	f[0][0] = 0;
	for (int i = 1; i <= n; ++i) {
		f[i][0] = f[i - 1][0] + (i <= n1) * a[i].first;
		for (int j = 1; j < i && j <= n2; ++j) {
			f[i][j] = max(f[i - 1][j] + (i - j <= n1) * a[i].first, f[i - 1][j - 1] + a[i].second);
		}
		if (i <= n2) f[i][i] = f[i - 1][i - 1] + a[i].second;
	}
	for (int i = n1 + n2; i <= n; ++i)
		res = max(res, f[i][n2]);
	return res > 0;
}
void work()	
{
	scanf("%d%d%d", &n, &n1, &n2);
	for (int i = 1; i <= n; ++i) {
		scanf("%d%d%d%d", &q1[i], &c1[i], &q2[i], &c2[i]);
	}
	double f = 0, r = 20000;
	for (int tmp = 0; tmp < 70; ++tmp) {
		double mid = (f + r) / 2;
		if (ok(mid)) f = mid;
			else r = mid;
	}
	printf("%.6f\n", (f+r)/2);
}
int main()
{
	int cases;
	scanf("%d", &cases);
	for (int tcase = 1; tcase <= cases; ++tcase) {
		printf("Case #%d: ", tcase);
		work();
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值