Jury Compromis-Poj1015-Java版-思维dp

引用晶之王的文字描述:
题意:

从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();
		}
	}
}

反思: 这题写了好久,也参考了别人。这种题为啥那么难想,是不是自己做的少?理解题意是一部分, 思维和经验是一部分,太弱了 ,欲哭无泪

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值