NOI 4.5 动态规划 1980: 陪审团的人选

题目来源:http://noi.openjudge.cn/ch0405/1980/

1980: 陪审团的人选

总时间限制1000ms    内存限制65536kB

描述

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

控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从020。为了公平起见,法官选出陪审团的原则是:选出的m个人,必须满足辩方总分和控方总分的差的绝对值最小。如果有多种选择方案的辩方总分和控方总分的之差的绝对值相同,那么选辩控双方总分之和最大的方案即可。

输入

输入包含多组数据。每组数据的第一行是两个整数nmn是候选人数目,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 

来源

扫描二维码关注公众号,回复: 2128380 查看本文章

Southwestern European Regional Contest 1996, POJ 1015, 程序设计实习2007

--------------------------------------------------------------

思路

比较难的动态规划问题,有点枚举的味道。

dp[i][j]: j个候选人使控辩差为i的方案中使控辩和最大的方案的控辩和, dp[i][j]==-1表示j个人控辩差不可能达到i(太绕啦)

path[i][j] = k: 回溯数组,dp[i+dif[k]][j]的情况是由于选了第j-1步时选了第path[i][j-1]即第k个候选人造成的

每选择一个候选人,枚举所有可能的控辩差,用dp数组记录控辩和,用path数组记录选择的候选人编号(用于回溯判断是否重复选择和结果输出)。当m个陪审团名额枚举完后,选控辩差最小中控辩和最大的解输出。

由于控辩差可正可负,在做dp数组下标时要加一个偏移量BIAS使之为非负数。

-----------------------------------------------------

代码 

#include<iostream>
#include<fstream>
#include<cstring>
#include<algorithm>
using namespace std;

int n,m;
const int DMAX = 805;
const int BIAS = 400;
const int NMAX = 205;
const int MMAX = 25;
int dp[DMAX][MMAX] = {};// dp[i][j]: 取j个候选人使控辩差为i的方案中使控辩和最大的方案的控辩和,-1表示j个人控辩差不可能达到i
int path[DMAX][MMAX] = {};	// path[i][j] = k: 回溯,dp[i+dif[k]][j]的情况是由于选了第j-1步时选了第path[i][j-1](k)个候选人造成的
int add[NMAX] = {};										// 控辩双方分数之和
int	dif[NMAX] = {};										// 控辩双方分数之差
int sol[MMAX] = {};

bool is_valid(int i, int j, int k)					// 当前的dp[i][j+b[k]]是否有效,无效的原因有1. 不是最大值; 2. 使用了已使用过的候选人
{
	if (dp[i][j-1]+add[k] <= dp[i+dif[k]][j])		// dp[i+dif[k]][j]已达到且更大
	{
		return 0;
	}
	while (--j)										// 用path回溯,看k是不是已经使用过
	{
		if (k == path[i][j])						// 如果k在第j步使用过
		{
			return 0;
		}
		i -= dif[path[i][j]];
	}
	return 1;
}


int main()
{
#ifndef ONLINE_JUDGE
	ifstream fin ("0405_1980.txt");
	int i,a,b,j,k,mysum,mydif,sum1,sum2,cnt=1;
	while (fin >> n >> m)
	{
		if (n==0)
		{
			break;
		}
		for (i=0; i<n; i++)
		{
			fin >> a >> b;
			add[i] = a + b;
			dif[i] = a - b;
		}
		memset(dp, 0, sizeof(dp));
		memset(path, 0, sizeof(path));
		for (j=0; j<MMAX; j++)
		{
			for (i=0; i<DMAX; i++)
			{
				dp[i][j] = -1;
			}
		}
		dp[BIAS][0] = 0;
		for (j=1; j<=m; j++)
		{
			for (i=BIAS-20*(j-1); i<=BIAS+20*(j-1); i++)
			{
				if (dp[i][j-1]!=-1)
				{
					for (k=0; k<n; k++)
					{
						if (is_valid(i,j,k))
						{
							dp[i+dif[k]][j] = dp[i][j-1] + add[k];
							path[i+dif[k]][j] = k;
						}
					}
				}
			}
		}
		for (i=0; i<=m*20; i++)				// 从0开始遍历寻找绝对值最小的控辩差
		{
			if (dp[BIAS-i][m]!=-1 && dp[BIAS+i][m]==-1)		// 如果-i是绝对值最小的控辩差
			{
				mydif = -i;									// 总控辩差
				mysum = dp[BIAS-i][m];						// 总控辩和
				break;
			}
			else if (dp[BIAS-i][m]==-1 && dp[BIAS+i][m]!=-1)// 如果+i是绝对值最小的控辩差
			{
				mydif = i;
				mysum = dp[BIAS+i][m];
				break;
			}
			else if (dp[BIAS-i][m]!=-1 && dp[BIAS+i][m]!=-1)// 如果+i/-i都是合理的值
			{
				if (dp[BIAS-i][m]>dp[BIAS+i][m])			// 取控辩和的大者
				{
					mydif = -i;
					mysum = dp[BIAS-i][m];
				}
				else
				{
					mydif = i;
					mysum = dp[BIAS+i][m];
				}
				break;
			}
		}
		j = m;
		i = BIAS+mydif;
		while (j--)
		{
			sol[j] = path[i][j+1];
			i -= dif[path[i][j+1]];
		}
		sort(sol, sol+m);
		sum1 = (mysum + mydif) / 2;							// 控方和
		sum2 = (mysum - mydif) / 2;							// 辩方和
		cout << "Jury #" << (cnt++) << endl;
		cout << "Best jury has value " << sum1 << " for prosecution and value " << sum2 << " for defence: " << endl;
		for (j=0; j<m; j++)
		{
			cout << " " << (sol[j]+1);						// 候选人编号从1开始
		}
		cout << endl << endl;
	}
	fin.close();
#endif
#ifdef ONLINE_JUDGE
	int i,a,b,j,k,mysum,mydif,sum1,sum2,cnt=1;
	while (cin >> n >> m)
	{
		if (n==0)
		{
			break;
		}
		for (i=0; i<n; i++)
		{
			cin >> a >> b;
			add[i] = a + b;
			dif[i] = a - b;
		}
		memset(dp, 0, sizeof(dp));
		memset(path, 0, sizeof(path));
		for (j=0; j<MMAX; j++)
		{
			for (i=0; i<DMAX; i++)
			{
				dp[i][j] = -1;
			}
		}
		dp[BIAS][0] = 0;
		for (j=1; j<=m; j++)
		{
			for (i=BIAS-20*(j-1); i<=BIAS+20*(j-1); i++)
			{
				if (dp[i][j-1]!=-1)
				{
					for (k=0; k<n; k++)
					{
						if (is_valid(i,j,k))
						{
							dp[i+dif[k]][j] = dp[i][j-1] + add[k];
							path[i+dif[k]][j] = k;
						}
					}
				}
			}
		}
		for (i=0; i<=m*20; i++)				// 从0开始遍历寻找绝对值最小的控辩差
		{
			if (dp[BIAS-i][m]!=-1 && dp[BIAS+i][m]==-1)		// 如果-i是绝对值最小的控辩差
			{
				mydif = -i;									// 总控辩差
				mysum = dp[BIAS-i][m];						// 总控辩和
				break;
			}
			else if (dp[BIAS-i][m]==-1 && dp[BIAS+i][m]!=-1)// 如果+i是绝对值最小的控辩差
			{
				mydif = i;
				mysum = dp[BIAS+i][m];
				break;
			}
			else if (dp[BIAS-i][m]!=-1 && dp[BIAS+i][m]!=-1)// 如果+i/-i都是合理的值
			{
				if (dp[BIAS-i][m]>dp[BIAS+i][m])			// 取控辩和的大者
				{
					mydif = -i;
					mysum = dp[BIAS-i][m];
				}
				else
				{
					mydif = i;
					mysum = dp[BIAS+i][m];
				}
				break;
			}
		}
		j = m;
		i = BIAS+mydif;
		while (j--)
		{
			sol[j] = path[i][j+1];
			i -= dif[path[i][j+1]];
		}
		sort(sol, sol+m);
		sum1 = (mysum + mydif) / 2;							// 控方和
		sum2 = (mysum - mydif) / 2;							// 辩方和
		cout << "Jury #" << (cnt++) << endl;
		cout << "Best jury has value " << sum1 << " for prosecution and value " << sum2 << " for defence: " << endl;
		for (j=0; j<m; j++)
		{
			cout << " " << (sol[j]+1);						// 候选人编号从1开始
		}
		cout << endl << endl;
	}
#endif
}


猜你喜欢

转载自blog.csdn.net/da_kao_la/article/details/81007129
4.5