poj 1015

#include<iostream>
#include<cstdio>
#include<string.h>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 205;
const int maxm = 25;
int dp[maxm][maxn << 2];
int rec[maxm][maxn << 2];
int p[maxn], d[maxn];
int n, m;
const int bias = 405;
bool isvalid(int odr, int mk, int id){
	int tmp = odr;
	while(odr > 0){
		if(id == rec[odr][mk]){
			return false;
		}
		int tmpid = rec[odr][mk];
		odr -= 1;
		mk -= d[tmpid] - p[tmpid];
	}
	return true;
}
void print(int odr, int mk){
	vector<int> candidt;
	int dd = 0, pp = 0;
	while(odr > 0){
		candidt.push_back(rec[odr][mk]);
		int tmpid = rec[odr][mk];
		mk -= d[tmpid] - p[tmpid];
		odr -= 1;
		dd += d[tmpid];
		pp += p[tmpid];
	}
	sort(candidt.begin(), candidt.end());
	printf("Best jury has value %d for prosecution and value %d for defence:\n",pp,dd);
	int size = candidt.size();
	for(int i = 0; i < size; ++i){
		printf(" %d",candidt[i]);
	}
	printf("\n\n");
}
int main(){
	int cse = 1;
	while(scanf("%d%d",&n, &m)!=EOF){
		if(n == 0 && m == 0){
			break;
		}
		printf("Jury #%d\n", cse++);
		memset(dp, -1, sizeof(dp));
		memset(rec, -1, sizeof(rec));
		for(int i = 1; i <= n; ++i){
			scanf("%d%d", p + i, d + i);
		}
		int uplimit = maxn << 2;
		dp[0][bias] = 0;
		for(int j = 1; j <= m; ++j){
			for(int k = 0; k < uplimit; ++k){
				for(int i = 1; i <= n; ++i){
					if(k + p[i] - d[i] >= 0 && dp[j - 1][k + p[i] - d[i]] != -1 && (dp[j - 1][k + p[i] - d[i]] + p[i] + d[i]) > dp[j][k] && isvalid(j - 1, k + p[i] - d[i], i)){
						dp[j][k] = dp[j - 1][k + p[i] - d[i]] + p[i] + d[i];
						rec[j][k] = i;
					}
				}
			}
		}	
		int ty = 0;
		while(bias - ty >= 0 && bias + ty < uplimit && dp[m][bias - ty] == -1 && dp[m][bias + ty] == -1){
			ty++;
		}
		if(dp[m][bias - ty] != -1){
			if(dp[m][bias + ty] != -1){
				if(dp[m][bias - ty] > dp[m][bias + ty]){
					print(m, bias - ty);
				}
				else{
					print(m, bias + ty);
				}
			}
			else{
				print(m, bias - ty);
			}
		}
		else{
			print(m, bias + ty);
		}
	}
	return 0;
}

题目大意:给定n个人(n <= 200)和每个人的两个属性d, p(d,p 在0-20之间的整数)要求给定m后,从中选出m个人(m <= 20), 使得这m个人的d值的和与p值的和的差的绝对值最小,当多种选法时,选出使得这m个人的d值与p值的和最大的一组

思路:由于一共选的人数不超过20个,且每个人d与p的差值在-20到20之间,所以任意一种选法的d值和与p值和在-400到400之间,可以用一个二维数组记录状态如下

dp[i][j]表示选了i个人,且其d与p的差为j - 405 (这个处理是为了将差值平移一下,让所有的状态下标为非负)时,其d值的和与p值的和的最大值; rec[i][j] 表示当dp[i][j]恰好选取这个最大值时,选择的人的下标编号

状态转移如下:

dp[i][j] = max {dp[i-1][j + p[k] - d[k]]} for all person[k] not repeatly used in the set

起始状态为: dp[0][bias] = 0, 表示不选择任何一个人且差的绝对值为0时,最大的和也为0,其余状态都不可达,记为-1;利用dp[i-1][j + p[k] - d[k]]计算dp[i][j]时,也要注意验证dp[i-1][j+p[k]-d[k]]是否可达。

这道题比较难想,看其他博客的参考也错了几个地方,后改过来的,写在下面以后注意:

  1. 更新dp的三个for 循环最外层为枚举人的个数,然后枚举差,最后枚举人,为了避免重复用一个人,需要每次都验证;而不是在最外层枚举人,在最外层枚举人默认为dp的顺序是按照人的顺序来的,而实际上并没有这种顺序关系

  2. 初始状态只有dp[0][bias] = 0,而不是二维数组dp的第0维都是0,验证状态是否可达是必须的,而且应该符合dp本身的含义

  3. 最后求选出的 m个人的 d和p的和的时候,也可以不像我这样把路径也求出来,因为知道了m和mk, 所以就知道了|D-P|和D+P,所以可以直接作和差求D和P 


你可能感兴趣的:(poj 1015)