【题目大意】
Alice新开了一家公司,它的下面有两个项目,分别需要N1和N2个人来完成。现在有N个人前来应聘,于是Alice通过面试来决定他们中的哪些人会被录用。
Alice在面试中,会仔细考察他们能如何为公司的项目带来收益。她给每个人打了两个分值Q1和Q2,表示他加入第一个和第二项目分别能带来的收益值。同时,她也会仔细考察他们每个人的缺点,并且给每人打了另两个分值C1和C2,表示他们进入每个项目可能带来的负面效应。Alice心目中的最优决策是,在决定好录用哪些人以及每个人在哪个项目下工作之后,他们为公司带来的收益总和,除以他们为项目带来的负面效应总和,这个比值要最大。你能帮他计算出在最优决策下,这个比值为多少吗?
前来应聘的人数总是大于等于两个项目需求人数的总和,因此Alice一定会恰好招N1+N2个人,分配给第一个项目N1个人,分配给第二个项目N2个人,没有人会同时属于两个项目。
【分析】
题目要求的是 SUM(Q)/SUM(C) 的最大值,假设最大值为ans , 则在最优情况下 SUM(Q)/SUM(C)=ans ===> SUM(Q)=ans*SUM(C)=SUM(ans*C)
==> SUM(Q)-SUM(ans*C)=0 ==> SUM(Q-ans*C)=0
看来关键就在于 SUM(Q-ans*C) 了,如果 SUM(Q-ans*C)>0 则说明 ans 还可以在一定的限度内增加,使相减后的和再减小趋近于0。由于和最小为0,即使 Qi-ans*Ci 都小于0,也不会出现 SUM(Q-ans*C)<0 的情况。对于某一固定的 ans ,SUM(Q-ans*C) 的值也会应为人员的选取不同而不同,但是可以肯定的是只要确定某种选择方案,SUM(Q-ans*C) 的值一定随 ans 的增大而减小;根据前面的说明,要使 ans 尽可能增大,对于每个 ans 的SUM(Q-ans*C)就要尽可能大,这里就要用到动态规划了。
dp[i][j] (j<=i) 表示前 i 个人,其中 j个人到项目 2 中,不多于 i-j 个人到项目1中。p[i]=pair(Q1-ans*C1,Q2-ans*C2)
dp[i][j]=max{dp[i-1][j]+(i-j<=n1)*p[i].first,dp[i-1][j-1]+p[i].second} ( j<i && j<=n2)
边界: dp[i][0]=dp[i-1][0]+(i<=n1)*p[i].first (i个人全进项目1,乘(i<=n1)是为了看项目1如否还可以加人)
dp[i][i] = dp[i-1][i-1]+p[i].second; (i个人全进项目2,必须满足条件 (i<=n2) ,可以加入项目2)
SUM(Q-ans*C) 的最大值为 max{ dp[j][n2] } (j>=n1+n2, j<=n)
由于 ans 对 SUM(Q-ans*C) 的影响的单调的 (SUM(Q-ans*C) 总是用DP求出尽可能大的值),ans 越大,SUM(Q-ans*C)的最大值 就会减少 。所以要做的就是二分 ans ,如果 SUM(Q-ans*C) 大于 0,可以提高下限,如果 SUM(Q-ans*C) 等于0,并不能说明 ans 达到最优解,有可能是超过最优解,所以要减小上限,最优解定在上下限内;这样多做几次二分(有限次数),就可以夹逼求出 ans 的最优解。
附上别人的代码,我也是看这代码有所启发的。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <map> 6 #include <string> 7 #include <cstring> 8 #include <sstream> 9 #include <algorithm> 10 11 using namespace std; 12 13 const int MAXN = 505; 14 15 int n, n1, n2; 16 int q1[MAXN], c1[MAXN], q2[MAXN], c2[MAXN]; 17 pair<double, double> a[MAXN]; 18 double f[MAXN][MAXN]; 19 bool ok(double ans) 20 { 21 for (int i = 1; i <= n; ++i) { 22 a[i] = make_pair(q1[i] - ans * c1[i], q2[i] - ans * c2[i]); 23 } 24 sort(a + 1, a + n + 1); 25 reverse(a + 1, a + n + 1); 26 double res = 0; 27 f[0][0] = 0; 28 for (int i = 1; i <= n; ++i) { 29 f[i][0] = f[i - 1][0] + (i <= n1) * a[i].first; 30 for (int j = 1; j < i && j <= n2; ++j) { 31 f[i][j] = max(f[i - 1][j] + (i - j <= n1) * a[i].first, f[i - 1][j - 1] + a[i].second); 32 } 33 if (i <= n2) f[i][i] = f[i - 1][i - 1] + a[i].second; 34 } 35 for (int i = n1 + n2; i <= n; ++i) 36 res = max(res, f[i][n2]); 37 return res > 0; 38 } 39 void work() 40 { 41 scanf("%d%d%d", &n, &n1, &n2); 42 for (int i = 1; i <= n; ++i) { 43 scanf("%d%d%d%d", &q1[i], &c1[i], &q2[i], &c2[i]); 44 } 45 double f = 0, r = 20000; 46 for (int tmp = 0; tmp < 70; ++tmp) { 47 double mid = (f + r) / 2; 48 if (ok(mid)) f = mid; 49 else r = mid; 50 } 51 printf("%.6f\n", (f+r)/2); 52 } 53 int main() 54 { 55 int cases; 56 scanf("%d", &cases); 57 for (int tcase = 1; tcase <= cases; ++tcase) { 58 printf("Case #%d: ", tcase); 59 work(); 60 } 61 return 0; 62 }