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
这道题就是典型动态规划(DP)题,不过这道题又比普通的背包问题复杂,因为还要输出选择的人,有一些技巧.
首先设置数组dp[i][k],表示在考虑前i个人的情况下,D-P=k时,D+P最大的情况,如果根本不可能让D-P=k,则dp[i][k]=-1.
再设置一个数字path[i][k],表示在dp[i-1][k0]中再加某个编号为j的人人变成dp[i][k]时,被选中的人,也就是说存储这种情况下被选的人,即path[i][k]=j.这样我们在知道最终的结果时倒过来推倒就能顺藤摸瓜把所有的人都找出来.
path数组也能用来查询一个人是否在当前的方案中被使用了,所有的方案都存在这个巨大的数组里面,有一种链表的感觉.
我们设置dp[21][801],path[21][801],至于为什么是801,除了需要让下标从1开始以外,还有一个就是保证所有的D-P都是正的,所以我们加了一个20*20来修正D-P的值,这样就可以直接用下标来表示当前的D-P.
第一次将dp[0][0]=0,其他的值置-1,将path所有的值置0
设所有的人数为n,需要选出m个人,偏移值offset=m*20,用于修正
我们以人数为循环参数,从1到m,在每个人数的方案中,再循环计算dp[i][k]是否可行,k的范围是0到2*offset,第三层循环是选人的循环,从1到n,如果发现dp[i-1][k]>=0,就计算dp[i-1][k]+he[j]是否大于dp[i][k+cha[j]],也就是将j号人选入当前方案是否比以前确定的方案更优,如果大于,就说明将j号人选入当前方案更优,再检查j是否已经在候选人中,如果不在,让dp[i][k+cha[j]]=dp[i-1][k]+he[j],同时记录下选的人是谁,让path[i][k+cha[j]]=j.
最终从dp[m][offset]处往两边找,第一个出现大于等于0的就是|D-P|最小的方案,如果有两个,谁存储的值大就选谁.
然后根据path顺藤摸瓜,再对找出的编号序列升序,最后根据offset计算一下D的和和P的和就行.
下面是AC代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int dp[21][801];
int path[21][801];
bool isUsed(int* cha,int i,int k,int j)
{
while (i>0 && path[i][k]!=j) {
k-=cha[path[i][k]];
i--;
}
return i?true:false;
}
int main()
{
int n,m,time=1;
while (cin>>n>>m && n) {
int t1,t2;
int* cha = new int[n+1];
int* he = new int[n+1];
for (int i=1; i<=n; i++) {
cin>>t1>>t2;
cha[i] = t1-t2;
he[i] = t1+t2;
}
int offset = m*20;
memset(dp, -1, sizeof(dp));
memset(path, 0, sizeof(path));
dp[0][offset] = 0;
for (int i=1; i<=m; i++) {
for (int k=0; k<=2*offset; k++) {
if (dp[i-1][k]>=0) {
for (int j=1;j<=n;j++) {
if (dp[i][k+cha[j]] < dp[i-1][k]+he[j] && !isUsed(cha,i-1,k,j))
{
dp[i][k+cha[j]] = dp[i-1][k]+he[j];
path[i][k+cha[j]] = j;
}
}
}
}
}
for (t1=0; t1<=offset; t1++) {
if (dp[m][offset-t1]>=0 || dp[m][offset+t1]>=0) break;
}
t1 = dp[m][offset-t1] > dp[m][offset+t1] ? offset-t1 : offset+t1;
cout<<"Jury #"<<time++<<endl<<"Best jury has value "<<(dp[m][t1]+t1-offset)/2<<" for prosecution and value "<<(dp[m][t1]-t1+offset)/2<<" for defence:"<<endl;
int* id=new int[m];
for(int i=0,j=m,k=t1;i<m;i++)
{
id[i]=path[j][k];
k-=cha[id[i]];
j--;
}
sort(id,id+m);
for(int i=0;i<m;i++) cout<<' '<<id[i];
cout<<endl<<endl;
delete[] cha;
delete[] he;
delete[] id;
}
return 0;
}