题目来源:http://noi.openjudge.cn/ch0405/1980/
1980: 陪审团的人选
总时间限制: 1000ms 内存限制: 65536kB
描述
在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n个人作为陪审团的候选人,然后再从这n个人中选m人组成陪审团。选m人的办法是:
控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0到20。为了公平起见,法官选出陪审团的原则是:选出的m个人,必须满足辩方总分和控方总分的差的绝对值最小。如果有多种选择方案的辩方总分和控方总分的之差的绝对值相同,那么选辩控双方总分之和最大的方案即可。
输入
输入包含多组数据。每组数据的第一行是两个整数n和m,n是候选人数目,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
来源
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
}