Jury Compromise POJ1015(多个“体积维度”的0/1背包)

题目:
在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。法官先随机挑选n 个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团。

选m 人的办法是:
参与诉讼的控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0 到20。第i个人的得分分别记为a[i],b[i]。

为了公平起见,法官选出陪审团的原则是:选出的m 个人必须满足:辩方总分D和控方总分P的差的绝对值|D-P|最小。如果有多种选择方案的|D-P| 值相同,那么选辩控双方总分之和D+P最大的方案即可。

求最终的陪审团获得的辩方总分D、控方总值P,以及陪审团人选的编号(方案)。1<=N<=200,1<=M<=20

Input

The input file contains several jury selection rounds. Each round starts with a line containing two integers n and m. n is the number of candidates and m the number of jury members.
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

For each round output a line containing the number of the jury selection round (‘Jury #1’, ‘Jury #2’, etc.).
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

思路:
这是一道具有多个“体积维度”的0/1背包问题。
0/1背包基本模型,只考虑消耗背包容量,关心价值。
而这道题消耗人数M,关心的是辩方总分D和控方总分P的差的绝对值|D-P|最小。

把N个候选人看作N个物品,每个物品有如下三种体积:
1、“人数”,每个“候选人”的人数都是1,最终要填满容积为M的背包。
2、“辩方得分”,即辩方给该候选人打的分数a[i],一个0~20的整数。
3、“控方得分”,即控方给该候选人打的分数b[i],一个0~20的整数。

因此,我们依次考虑每个候选人是否选入评审团,当外层循环到i时,表示已经考虑了前i个候选人的入选情况,此时用Boolean数组F[j,d,p]表示已有j人被选入评审团,当前辩方总分为D,控方总分为P的状态是否可行。
F[j,d,p]=F[j,d,p] or F[j-1,d-a[i],p-b[i]]
初值F[0,0,0]=true,其余均为false。
0/1背包,对每个产品选还是不选,选了F[j,d,p]为true,不选为true。
目标:找到一个状态F[M,d,p],满足F[M,d,p]=true,并且|d-p|尽量小,|d-p|相同时d+p尽量大。

上述算法没有很好地利用背包“价值”这一维度,还有很大的优化空间。实际上,我们可以把每个候选人辩、控双方得分的差a[i]=x-y作为该物品的“体积”之一,把辩控双方得分的和作为该物品的价值。

注意,以下a[i],b[i]表示不同的含义。
辩、控双方得分的差a[i]=x-y作为该物品的体积。
控辩双方对每个人的评价得分和用b[i]=x+y来存放。
当外层循环到i时,设F[i,j]表示选取 i 个人,其评价差之和为 j 的所有方案中总评价之和最大
F[i,j]=max(F[i-1,j],F[i-1,j-a[i]]+b[i])
初值:F[0,0]=0,其余均为负无穷。
目标:找到一个状态F[M,k],满足|k|尽量小,当|k|相同时F[M,k]尽量大。

在转移中,注意 j 这一维用倒序循环,即可保证每个候选人只会被选一次。

本题还要求输出评审团入选的具体方案,我们采用记录转移路径的方法,即额外建立一个数组path,其中path[i,j]记录状态F[i,j]的最大值是选了哪一名候选人而得到的。

在求出最优解后,我们沿着数组path记录的转移路径,不断从状态F[i,j]递归到F[i-1,j-a[path[i,j]]。递归过程中所有path[i,j]就构成了评审团的人选。

三重循环,找出各种人数在各个评价差上的最大总评价之和,评价差的范围是[-20 * m,20 * m],m最大取值是20 。所以区间是[-400,400],数组下标不能为负,所以一个修正值now=20*m 保证正确性。区间平移,计算出来最大区间也不过800。

总而言之,就是把背包问题的 一维作为人数,二维作为差值,记录和值从1到m,枚举每一个情况,遇到差值重合则取总和较大一组,最后在第m行中由中间开始遍历寻找最近的,也就是差值最小的值。
有几个注意点:
1 用path[][] 记录路径,枚举时要回溯查找 是否已经用过某个人。
2 由于差值可能为负,数组下表不能为负,所以一个修正值 now=20*m 保证正确性

代码:

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int f[30][810],path[30][810],a[210],b[210];
int n,m,now;
inline bool find(int i, int j, int k)
{
	if(i == 0) return false;
	if(path[i][j] == k) return true;
	return find(i-1,j-a[path[i][j]],k);
}
int main()
{
	int cas(0);//int cas=0;
	while(scanf("%d%d",&n,&m),n+m)
	{
		memset(f,-1,sizeof f);
		now = 20 * m;f[0][now] = 0;
		for(int i = 1; i <= n; i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			a[i] = x-y;
			b[i] = x+y;
		}
		/*
	    对于i=1时,j从0到now-1,F[i-1][j]它的值的二进制全是1,
	    ~f[i-1][j],按位取反后,就是0,不做考虑,我们设F[0][now]=0,目的就是j从20*m开始,
	    如果两积分差是正的,我们就从now以后开始,如果是负的,没关系,正好
	    从20*m前面开始,这样始终保证下标是正的。
	    状态转移结束后,我们判断now后有没有j的有效组合,
	    再判断now前面有没有j的有效组合,两侧的j的值是相同的,就是数轴一样,
	    now就是数轴的原点。
		*/
		for(int i = 1; i <= m; i++)//选m个人
			for(int j = 0;j <= 2 * now; j++)
				if(~f[i-1][j])//判断前i-1个候选人有积分差有是j这种组合没有。
					for(int k = 1;k <= n; k++)//每次都从这n个人中选没有被选过的,找出相同j下,控辩双方积分和最大
						if(!find(i-1,j,k))//如果第k 个人没有被选过
							if(f[i][j+a[k]] < f[i-1][j] + b[k])
							{
								f[i][j+a[k]] = f[i-1][j] + b[k];
								path[i][j+a[k]] = k;
							}

		int ans(0x7fffffff),num,sum;
		for(int i = now;i <= 2*now;i++)
		if(~f[m][i])
		{
			ans = i - now;
			num = i;
			sum = f[m][i];
			break;
		}
		for(int i = now; i >= 0; i--)
		if(~f[m][i])
			if(now - i < ans ||(now - i == ans && f[m][i] > sum))
			{
				ans = i - now;
				num = i;
				sum = f[m][i];
				break;
			}
			/*从now两侧找到满足题意的控辩双方积分差,
			把这个积分差保留到num中,后面输出路径中要用到*/
		printf("Jury #%d\n",++cas);
		int x = (sum+ans)>>1,y = sum - x;
		printf("Best jury has value %d for prosecution and value %d for defence: \n",x,y);
		x=m,y=num;
		int tot(0);
		int out[210];
		while(x)
		{
			out[++tot] = path[x][y];
			y -= a[path[x][y]];
			x--;
		}
		sort(out+1,out+tot+1);
		for(int i = 1; i <= tot; i++)
		printf(" %d",out[i]);
		puts("\n");
	}
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值