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
Source
中文题干:
描述控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0到20。为了公平起见,法官选出陪审团的原则是:选出的m个人,必须满足辩方总分和控方总分的差的绝对值最小。如果有多种选择方案的辩方总分和控方总分的之差的绝对值相同,那么选辩控双方总分之和最大的方案即可。
4 2 1 2 2 3 4 1 6 2 0 0
Jury #1 Best jury has value 6 for prosecution and value 4 for defence: 2 3
我觉得做到这道题适合来畅谈一下了。
首先,我要提供一种错误的思路,是我今天WA了一下午但是依然百思不得其解的思路。
如果您急于求成,可以直接跳过;如果你闲得无聊,请继续:
假如我们设f[i][j]为前i个人选出了j个人的差的最小值,那么我们只需要从f[i-1][j]和f[i-1][j-1]考虑转移就行了,分别是选当前和不选当前的情况。然后最后输出f[n][m],问题就迎刃而解了。这不是很合理吗?
再次声明,这个思路是错误的,因为这里存在一个盲区:
本道题的题意非常明确,简单说就是:把选的m个人的所有的控方分数都加起来,再把所有的辩方分数都加起来,再相减,让绝对值最小,同时和最大。可以感受一下,存在这样一种情况,就是有一组数据,在这个范围内没有被选到,而在下一个范围内却被选到了。其实这就打破的dp的原则:最优子结构和无后效性。
所以结论是,这样的dp是无法建立的。
那么怎么让它没有后效性呢?通常的办法是加维。本题加维的方法应该也是可以实现的。但是我们不妨换一种思路:可不可以考虑其他的状态转移?
下面是正确的解法,来自,各种神犇的题解:
其实这种转移方法和加维的性质是差不多的,都是通过控制差值取消了后效性。设p[i][k]为记录当选了 i 个人辩控差是k的时候辩控和最大值,path[i][k]为记录当选了i个人辩控差是k 然后对应dp数组值<辩控和最大>的前一个人的编号
还需要一个修正值:fix。这个是防止出现负数下标的。相减的时候可能会出现负数。
其实这道题理解了状态的表示就好说了。大神们看到这一步估计就已经开始匆匆写代码了。如果不会处理细节问题的话,可以好好读读代码。
【代码】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
struct hp{
int p,d,s,v;
}pre[210];
int path[25][810],dp[25][810];
int id[25],n,m,T;
bool select(int j,int k,int i){
while(j>0&&path[j][k]!=i){
k-=pre[path[j][k]].v;
j--;
}
if(j==0) return true;
else return false;
}
int main(){
while(~scanf("%d%d",&n,&m))
{
if(!n&&!m) return 0;
memset(path,0,sizeof(path));
memset(pre,0,sizeof(pre));
memset(dp,-1,sizeof(dp));
for(int i=1;i<=n;++i){
scanf("%d%d",&pre[i].p,&pre[i].d);
pre[i].s=pre[i].p+pre[i].d;
pre[i].v=pre[i].p-pre[i].d;
}
int fix=m*20;
dp[0][fix]=0;
for (int i=1;i<=m;++i)
for (int k=0;k<=fix*2;++k)
if (dp[i-1][k]>=0)
for (int j=1;j<=n;++j)
if (dp[i-1][k]+pre[j].s>dp[i][k+pre[j].v]&&select(i-1,k,j)){
dp[i][k+pre[j].v]=dp[i-1][k]+pre[j].s;
path[i][k+pre[j].v]=j;
}
int k;
for(k=0;k<=fix;++k)
if(dp[m][fix-k] >= 0 || dp[m][fix+k] >= 0) break;
int div=dp[m][fix-k]>dp[m][fix+k]?(fix-k):(fix+k);
int D = (dp[m][div] - div + fix)/2;
int P = (dp[m][div] + div - fix)/2;
printf("Jury #%d\n", ++T);
printf("Best jury has value %d for prosecution and value %d for defence:\n", P, D);
for(int i=0,j=m,k=div;i<m;++i){
id[i] = path[j][k];
k -= pre[id[i]].v;
j--;
}
sort(id,id+m);
for(int i=0;i<m;++i)
printf(" %d", id[i]);
printf(" \n\n");
}
}