Description
Based on the grades of the two parties, the judge selects the jury. In order to ensure a fair trial, the tendencies of the jury to favour either defence or prosecution should be as balanced as possible. The jury therefore has to be chosen in a way that is satisfactory to both parties.
We will now make this more precise: given a pool of n potential jurors and two values di (the defence's value) and pi (the prosecution's value) for each potential juror i, you are to select a jury of m persons. If J is a subset of {1,..., n} with m elements, then D(J ) = sum(dk) k belong to J
and P(J) = sum(pk) k belong to J are the total values of this jury for defence and prosecution.
For an optimal jury J , the value |D(J) - P(J)| must be minimal. If there are several jurys with minimal |D(J) - P(J)|, one which maximizes D(J) + P(J) should be selected since the jury should be as ideal as possible for both parties.
You are to write a program that implements this jury selection process and chooses an optimal jury given a set of candidates.
Input
These values will satisfy 1<=n<=200, 1<=m<=20 and of course m<=n. The following n lines contain the two integers pi and di for i = 1,...,n. A blank line separates each round from the next.
The file ends with a round that has n = m = 0.
Output
On the next line print the values D(J ) and P (J ) of your jury as shown below and on another line print the numbers of the m chosen candidates in ascending order. Output a blank before each individual candidate number.
Output an empty line after each test case.
Sample Input
4 2
1 2
2 3
4 1
6 2
0 0
Sample Output
Jury #1
Best jury has value 6 for prosecution and value 4 for defence:
2 3
Hint
在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n 个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团。选m 人的办法是:控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0 到20。为了公平起见,法官选出陪审团的原则是:选出的m 个人,必须满足辩方总分D和控方总分P的差的绝对值|D-P|最小。如果有多种选择方案的|D-P| 值相同,那么选辩控双方总分之和 D + P 最大的方案即可。
输出:
选取符合条件的最优m个候选人后,要求输出这m个人的辩方总值D和控方总值P,并升序输出他们的编号。
思路分析:
为叙述问题方便,现将任一选择方案中,辩方总分和控方总分之差简称为“辩控差”,辩方总分和控方总分之和称为“辩控和”。第i 个候选人的辩方总分和控方总分之差记为V(i),辩方总分和控方总分之和记为S(i)。
现用dp(j, k)表示,取j 个候选人,使其辩控差为k 的所有方案中,辩控和最大的那个方案(该方案称为“方案dp(j, k)”)的辩控和。
并且,我们还规定,如果没法选j 个人,使其辩控差为k,那么dp(j, k)的值就为-1,也称方案dp(j, k)不可行。本题是要求选出m 个人,那么,如果对k 的所有可能的取值,求出了所有的dp(m, k) (-20×m≤ k ≤ 20×m),那么陪审团方案自然就很容易找到了。 问题的关键是建立递推关系。需要从哪些已知条件出发,才能求出dp(j, k)呢?显然,方案dp(j, k)是由某个可行的方案dp(j-1, x)( -20×m ≤ x ≤ 20×m)演化而来的。
可行方案dp(j-1, x)能演化成方案dp(j, k)的必要条件是:存在某个候选人i,i 在方案dp(j-1, x)中没有被选上,且x+V(i) = k。在所有满足该必要条件的dp(j-1, x)中,选出 dp(j-1, x) + S(i) 的值最大的那个,那么方案dp(j-1, x)再加上候选人i,就演变成了方案 dp(j, k)。
这中间需要将一个方案都选了哪些人都记录下来。不妨将方案dp(j, k)中最后选的那个候选人的编号,记在二维数组的元素path[j][k]中。那么方案dp(j, k)的倒数第二个人选的编号,就是path[j-1][k-V[path[j][k]]]。假定最后算出了解方案的辩控差是k,那么从path[m][k]出发,就能顺藤摸瓜一步步回溯求出所有被选中的候选人。
初始条件,只能确定dp(0, 0) = 0,其他均为-1。由此出发,一步步自底向上递推,就能求出所有的可行方案dp(m, k)( -20×m ≤ k ≤ 20×m)。实际解题的时候,会用一个二维数组dp 来存放dp(j, k)的值。而且,由于题目中辩控差的值k 可以为负数,而程序中数租下标不能为负数,所以,在程序中不妨将辩控差的值都加上修正值fix=400,以免下标为负数导致出错。
为什么base=400?这是很显然的,m上限为20人,当20人的d均为0,p均为20时,会出现辨控差为-400。修正后回避下标负数问题,区间整体平移,从[-400,400]映射到[0,800]。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>
#include <string>
#include <map>
using namespace std;
#define N 220
#define met(a, b) memset(a, b, sizeof(a))
#define INF 0xffffff
const long long Max = 2000000000;
typedef long long LL;
int D[N], P[N];
int dp[30][1000]; ///dp[i][k]代表选 i 个人, 辩控差为 k 的辩控和最大
int pre[30][1000]; ///pre[i][k] 存的是它是上次选的人
int Answer[N]; ///记录选中的这 m 个人的编号
int main()
{
int n, m, iCase=1;
while(scanf("%d%d", &n, &m), n||m)
{
int i, j, k, Min, t1, t2;
met(D, 0);
met(P, 0);
met(dp, -1);
met(pre, 0);
met(Answer, 0);
for(i=1; i<=n; i++)
scanf("%d%d", &D[i], &P[i]);
Min = m*20; ///它的辩控差最大为 m*20
dp[0][Min] = 0; ///起始状态要先置为 0
/// k 的最大取值范围是[-Min, Min], 但是数组不能表示负数, 因此将数组向右平移 Min,得到[0, 2*Min]
for(i=0; i<=m; i++)
{
for(k=0; k<=Min*2; k++)
{
if(dp[i][k]==-1) continue; ///如果存在,接着找 dp[i][k] 的下一个状态
for(j=1; j<=n; j++)
{
if(dp[i+1][k+D[j]-P[j]] < dp[i][k]+D[j]+P[j])
{
t1=i, t2=k;
while(t1>0 && pre[t1][t2]!=j)
{
t2 -= D[pre[t1][t2]] - P[pre[t1][t2]];
t1 --;
}
if(t1==0) ///当 t1 为 0 时,编号为 j 这个人在之前没有被选中过
{
dp[i+1][k+D[j]-P[j]] = dp[i][k] + D[j] + P[j];
pre[i+1][k+D[j]-P[j]] = j;
}
}
}
}
}
int ff = Min;
int num = 0, sum1=0, sum2=0;
///要选辩控差最小的,所求的 num 便是选 m 个人辩控差最小的
while(dp[m][ff-num]==-1 && dp[m][ff+num]==-1) num++;
if(dp[m][ff-num]>dp[m][ff+num]) t2 = ff-num;
else t2 = ff+num;
t1 = m;
for(i=1; i<=m; i++)
{
Answer[i] = pre[t1][t2];
sum1 += D[Answer[i]];
sum2 += P[Answer[i]];
t1--;
t2 -= D[Answer[i]] - P[Answer[i]];
}
printf("Jury #%d\n", iCase++);
printf("Best jury has value %d for prosecution and value %d for defence:\n", sum1, sum2);
sort(Answer+1, Answer+1+m);
for(i=1; i<=m; i++)
printf(" %d", Answer[i]);
printf("\n\n");
}
return 0;
}