引用晶之王的文字描述:
题意:
从n个人中选出m个人,选法为控方满意度之和s1与辩方满意度之和s2的差的绝对值最小,若有多种方案,则选择控方满意度之和s1与辩方满意度之和s2的和最大的一组,先输出这是第几组数据,下一行分别输出选择出来的方案中的控方满意度之和s1与辩方满意度之和s2。最后一行输出选择的人的编号,按从小到大的顺序排列。
思路:
用一个二维dp[i][k],第一维i代表已经选择了的人数,即1——m,第二维k代表枚举出来的可能出现的控方满意度之和s1与辩方满意度之和s2的差,不过这个差可能为负,即数组的第二维可能为负,因此我们可以让它向左偏移一下,具体实现看代码,这个二维dp的值就代表控方满意度之和s1与辩方满意度之和s2的和;则dp[i][k]可由dp[i-1][x]得来的条件是存在某个还没被选择的候选人j使得k=x+v[j]。v[j]数组代表第j个人的控方与辩方满意度之差,s[j]数组代表第j个人的控方与辩方满意度之和。这个时候我们就可以枚举1——n中的满足这个条件的j来动态规划。输出要求输出选择的人的编号,因此我们用一个path数组来存储选择的人的编号,path[i][k]的值为当前所选人的编号j,i代表这是选择的第几个人,与上文的i意义相同,k也与上文的意义相同。这时倒数第二个人的编号就是path[i-1][k-v[path[i][k]]],依次推回去就可得到选择的所有人的编号。
原文链接:https://blog.csdn.net/xinjunwang/article/details/79734103
看了这麽多是不是还不理解,没关系,我们把样例走一遍:
4 2
1 2 no1
2 3 no2
4 1 no3
6 2 no4
我们知道: 第一个人的差值是-1(v[1]=-1),和值是3 (s[1]=3) ,第二个人的差值是-1(v[2]=-1),和值是5 (s[2]=5),以此类推
题目要让我们找出来两个人的最优解(这两个人的差绝对值小于其他人,并且和尽量大)
我们一个一个来:
(1)首先找唯一一个人的最优解:第一次循环
no1: 差值-1 , 和是 3
no2: 差值 -1 ,和是5
no3: 差值3 ,和是5
no4:差值4 和是8
好了,为了我们使用方便, (毕竟我们一会要用这个差值作为数组的下标), 我们让所有的差值都加上 400
no1: 差值399, 和是 3
no2: 差值 399 ,和是5
no3: 差值403,和是5
no4:差值404 和是8
我们开一个数组,dp[a][b] , 意思是, 对于已经选出来的 a 个人来说,如果差值(a个人的叠加)是b , dp[a][b]就代表这a个人的 总和值
例如: 我们在第一次循环之后,得到如下结果
dp[1][399] = 5 , 请注意, 如果b一样,就选择和最大的那个!
dp[1][403] = 5
dp[1][404]=8
其他的 dp[1][0] , dp[1][1] ,dp[1][2] .....dp[1][799], dp[1][800]都是初始化的-1,
意思是根本不可能合成 0 , 1, 2 ,799, 800
(2)对于第二次循环:
首先知道,dp[2][1,2,3,…800]都是-1
然后:
我们开始 从 1 找到 800 ,看看哪个数是 第一步走过的数: 仅仅有三个:399 ,403 ,404
对于 399 , 这是我们在第一步选择了 no2 所产生的结果
因此 第二步不能选 no2 , 其他的都可以选的 ,那么我们得到了
dp[2] [ 399 + -1 ] = 3+3=6 //选择第一个人
dp[2][399 + 3] = 3+5=8; //选择第三个人
dp[2][399 + 4] = 3+8 = 10 //选择第四个人
对于 403,这是第一部选择了第三个人的后果,因此第二步只能选 1 ,2, 4了
dp[2][403 + -1] = 5+ 3 =8 // 选第一个人
dp[2][403 + -1]=5+5 =10 // 选第二个人
dp[2] [403 + 4] = 5+8 =13 //选第三个人
对于404也是一样
我们又发现,出现了两个 dp[2][403-1] , 还是老道理,选择 和比较大的 ,那么就是10
(3) 走完了上面两步,其实dp就结束了, 我们此使得到的 dp数组 我们想要:
dp[2][x] 中的x 最贴近 400 , 想想为什么,因为 400 就是0 (我们上面担心负数因此加了400), 所以,遍历一次 dp[2][] ,找出来那个 离 400 最近的 x ,比如 dp[2][399] 就比 dp[2][402] 更近。
实际的值就是 -1比2 更近。
这就是这个题目的思想, 程序中的判断是否 使用过 和 记录路径相信大家一看就知道了
import java.io.BufferedInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class poj1015 {
static int[][] dp, path;
static int[] v,s;
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
dp=new int[25][2000];
path=new int[25][2000];
v=new int[300];
s=new int[300];
List<Integer>list = new ArrayList<Integer>();
int tt=0;
while(sc.hasNext()) {
tt++;
int n =sc.nextInt();
int m=sc.nextInt();
if(n==0 &&m==0) break;
for(int i=0;i<n;i++) {int t1 =sc.nextInt(); int t2= sc.nextInt(); v[i]=t1-t2;s[i]=t1+t2;}
for(int i=0;i<25;i++)for(int j=0;j<2000;j++) {dp[i][j]=-1;path[i][j]=-1;}
dp[0][400]=0;
for(int i=0;i<m;i++) {
for(int k=0;k<=800;k++) {
if(dp[i][k] < 0) continue;
for(int j=0;j<n;j++) {
if(dp[i+1][k+v[j]] < dp[i][ k]+s[j]) {
// 这个j 是否用过,比如 第一次循环对应的这个 k用过1号,
//这里就能不能再是1号 , 可以是其他人,
int a = k;
int b =i;
while(b>0 && path[b][a]!=j) {
a-=v[path[b][a]];
b--;
}
if(b==0) {
dp[i+1][k+v[j]] = dp[i][k]+s[j];
path[i+1][k+v[j]]=j;
}
}
}
}
}
int a=400;
int b= 0;
//找最贴近400的值
while(dp[m][a-b] <0 && dp[m][a+b]<0) {
b++;
}
int res=0;
//和大的那个
if(dp[m][a-b]>dp[m][a+b])
res=a-b;
else
res=a+b;
System.out.println("Jury #"+tt);
System.out.println("Best jury has value "+(res-400+dp[m][res])/2+" for prosecution and value "+(400-res+dp[m][res])/2+" for defence: ");
int temp;
list.clear();
for(int i=0;i<m;i++) {
temp=path[m-i][res];
list.add(temp);
res-=v[temp];
}
Collections.sort(list);
for(int i:list) {
System.out.print(" "+(i+1));
}
System.out.println();
}
}
}
反思: 这题写了好久,也参考了别人。这种题为啥那么难想,是不是自己做的少?理解题意是一部分, 思维和经验是一部分,太弱了 ,欲哭无泪