中文:
描述
在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n个人作为陪审团的候选人,然后再从这n个人中选m人组成陪审团。选m人的办法是:
控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0到20。为了公平起见,法官选出陪审团的原则是:选出的m个人,必须满足辩方总分和控方总分的差的绝对值最小。如果有多种选择方案的辩方总分和控方总分的之差的绝对值相同,那么选辩控双方总分之和最大的方案即可。
输入
输入包含多组数据。每组数据的第一行是两个整数n和m,n是候选人数目,m是陪审团人数。注意,1<=n<=200, 1<=m<=20 而且 m<=n。接下来的n行,每行表示一个候选人的信息,它包含2个整数,先后是控方和辩方对该候选人的打分。候选人按出现的先后从1开始编号。两组有效数据之间以空行分隔。最后一组数据n=m=0
输出
对每组数据,先输出一行,表示答案所属的组号,如 ‘Jury #1’, ‘Jury #2’, 等。接下来的一行要象例子那样输出陪审团的控方总分和辩方总分。再下来一行要以升序输出陪审团里每个成员的编号,两个成员编号之间用空格分隔。每组输出数据须以一个空行结束。
样例输入
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
这道题有很多变量,m,审判的总差值,审判总分
我们把n当做常量不去考虑他
但是把剩下的三个全部写到一个二维数组里面f[j][k]
j是m的变量也就是选取的人数
k是差值
f是该差值和人数下最大的总分
然后我们使用f这个函数得到一个递推关系
那就是f[j+1][k+p[i]-v[i]]=f[j][k]+p[i]+v[i] when f[j][k]+p[i]+v[i]>f[j+1][k+p[i]-v[i]] or f[j+1][k+p[i]-v[i]] is empty
需要注意的是这个递推公式是固定f[j][k],得到f[j+1][k+p[i]-v[i]],而不是说f[j+1][k+p[i]-v[i]]有个公式可以由之前的数组得到
其他的全部写在注释里面去了
代码
import java.util.*;
import static java.lang.Math.*;
public class test2 {
public static void main(String[] args) {
//首先是标准化的输入输出
Scanner cin =new Scanner(System.in);
while(cin.hasNext()){
int n;
int m;
n=cin.nextInt();
m=cin.nextInt();
int p[]=new int[n+1];
int v[]=new int[n+1];
for(int i=1;i<=n;i++){
p[i]=cin.nextInt();
v[i]=cin.nextInt();
}
// System.out.println();
//这里我们需要一个bias,偏置,因为我们的审判团总差值是一个可正可负的数,但是数组的索引不能是负数,所以加一个偏置导致所有的审判总差值是一个正数
int bias=m*20;
//f[j][k]函数表示的是如果选取j个人,且这j个人的差值是k的情况下,最大的审判总分,
// 最大体现在后面的代码之中有一个判断f[j][k]+p[i]+v[i]>f[j+1][k+p[i]-v[i]],
// 表示如果有一个更大的值也满足j+1人,差值为k+p[i]-v[i],那么就更新f[j+1][k+p[i]-v[i]]。
int f[][]=new int [m+1][2*bias+1];
//注意需要初始化f为-1,这里的-1表示的是还没有一个这样的可能性存在,注意这里的-1有两个作用
//一个是在初次访问f[j][k]+p[i]+v[i]>f[j+1][k+p[i]-v[i]],的时候f[j+1][k+p[i]-v[i]]总是=1的,所以如果存在这种情况的话总能够保存下来,
//如果是第二次访问的话,那么就看谁大,放谁,这个就是题目之中说的如果差值相同,选择总分大的那个
for(int i=0;i<=m;i++){
Arrays.fill(f[i],-1);
}
//注意需要由一个初始的值,表示一个人也不选的时候差值为0(k=k_real+bias,所以差值为0 实际在数组之中的索引是bias) 它的总分也是0
f[0][bias]=0;
int path[][]=new int [m+1][2*bias+1];
//path是为了找到任意情况下f[j][k]的路径,也就是选取j个人,差值为k的情况下,总分最高的那个选取组合包含了哪些人
//path的具体意思是对于f[j][k]这种情况,最后一个选取的节点是谁。
//然后我们通过最后一个节点,知道f[j][k]这种情况,应该是从f[j-1][k-(p[path[j][k]]-v[path[j][k]])]
//这个意思是,由于我们知道了最后一个节点是path[j][k]],那么我们通过p[] and v[]数组得到最后一个节点的差值p[path[j][k]]-v[path[j][k]]
//对于选取j个人,差值为k的情况,去掉最后一个人就是选取j-1个人,差值为k-最后一个节点的差值的情况
//这里隐含了一个认知,就是选取j-1个人,差值为k-最后一个节点的差值的情况下,有很多种不同的选取方法,但是f函数得到的书总分最高的哪一种情况
//f[j][k]总是选取j-1个人,差值为k-最后一个节点的差值且总分最高的情况下加上最后一个节点的。否则的话f[j][k]就不是总分最高的了;
//综上所述,最后我们就得到了一个路径,包含了到f[j][k]情况下的所有节点
for(int i=0;i<=m;i++){
Arrays.fill(path[i],0);
}
int answer[]=new int[m];
// 用来在最后保存选取的人的编号
for(int j=0;j=0){
// 一开始的时候其实就是在f[0][bias]开始的
// 一般情况下就是表示有的差值是靠j个人无法得到的,所以我们从可能的情况下开始做
for(int i=1;i<=n;i++){
if(f[j][k]+p[i]+v[i]>f[j+1][k+p[i]-v[i]]){
// 正如上面我们解释为什么f赋初值为-1时所说
// 我们判断需要写入的值是否被写入,如果被写入我们写入的是不是比原来大
int t1=j;
int t2=k;
// 还要判断这个加入的节点是不是已经在路径之中了
while(path[t1][t2]!=i&&t1>0){
t2-=p[path[t1][t2]]-v[path[t1][t2]];
t1--;
}
if(t1==0){
// r如果不在,就赋值
path[j+1][k+p[i]-v[i]]=i;
f[j+1][k+p[i]-v[i]]=f[j][k]+p[i]+v[i];
}
}
}
}
}
}
int res=-1;
int res_index=-1;
// 接着我们寻找差值最小,总分最大的序列
for(int i=0;i<=bias;i++){
// 注意我们找差值最小的时候需要从两边开始找,bias右边和bias左边,因为差值有正有负,我们需要的是差值的绝对值最小
if(f[m][bias-i]!=-1||f[m][bias+i]!=-1){
// 这是找到了距离bias最近的不是-1的数,也就是差值最小的可能情况,也许有两种情况,所以接下里我们还需要比较那个总分大
res_index=f[m][bias-i]>f[m][bias+i]?bias-i:bias+i;
res=f[m][res_index];
int temp=res_index;
// 总分大的是res,接下来我们保存这种情况下的节点数
for(int j=m;j>=1;j--){
answer[j-1]=path[j][temp];
temp=temp-(p[ answer[j-1]]-v[ answer[j-1]]);
}
break;
}
}
//按照序号的升序排列
Arrays.sort(answer);
System.out.println(Arrays.toString(answer));
}
}
}



