初学DP,夜鱼就给我找了这么条难缠的“大鱼”,有点打击到我~~~~
问题:n個候選人,要挑m個組成陪審團。選人方法:控方和辯方根據喜歡程度為每個候選人打分(0~20),選出的這m個人,必須滿足辯方總分和控方總分的差的絕對值最小。如有多種方案,則選辯控雙方總分之和最大的方案即可。
輸入:1<=n<=200, 1<=m<=20, m<=n; pi,di(i=1,…,n)表控、辯打分數。
解决DP问题的关键在于列出递推方程,除此之外还要证明两点:最优子结构和重叠子问题。关于前者,采用的是“剪贴”技术,本质是反证法,本题的子问题很明显,当考虑i个人时,那么可以从i-1个人的最优方案中获得解的部分。关于后者,本题子问题的规模是越来越小的,直到边界i=1。列递推方程的关键在于有个好的状态描述,本题,子问题规模i个人,差要求最小,在此基础上要求和最大。最后的“要求”往往是“状态”的值,于是不难写出状态s[i,j],而根据该状态的定义,用小一级的子问题来表述之,就是下面所列的方程。s[i,j]表示i个人且差之和为j时所有方案中和之和的最大值。则s[i,j]=max{s[i-1,j-(d[k]-p[k])]+(d[k]+p[k])},(k不在s[i-1,*]中)
本题在输入的时候,是借鉴PKU(1002)的Hint部分,除了下面的格式之外,还可以while(scanf("%d %d", &n, &m),n||m)或while(cin>>n>>m,n||m).
题目最终是要求输出节点的,所以可以用hash表来记录,这样做还有个好处,那就是方程的“(i不在s[i-1,*]中)”部分同时也解决了,因为对访问过且采用的节点可以做记录,从而进行判断。本题是用二维数组来实现的,记号path[][]参考了别人的。
接下来就是算法的核心了,首先是方程的初始状态,也就是边界。因为是max,所以用s[1][20m+a[i]]<b[i]来筛选,并用path作记录。有了s[1][]就可以递推了,用s[i+1][j+a[k]]<s[i][j]+b[k]来筛选。由方程可看出是要用三个for循环的,分别对应i,j,k。注意除此之外最里面的for循环只是用来处理方程的“(k不在s[i-1,*]中)”部分而已。
剩下的部分都是细枝末节了。
**************************************************************************
#include "stdafx.h"
#include <algorithm>
#include <iostream>
using namespace std;
int main()
{
int s[21][805],path[21][805],a[205],b[205],c[21];
int p,d,n,m,min,cn=0,minus;
//输入
while(scanf("%d%d", &n, &m)&&n+m){
for(int i = 1; i <= n; ++i){
scanf("%d%d", &p, &d);
b[i] = p+d;
a[i] = p-d;
}
minus=20*m;
//初始化
memset(s, -1, sizeof(s));
memset(path, -1, sizeof(path));
s[0][minus] = path[0][minus] = 0;
//边界
for(int i = 1; i <= n; ++i){
if(s[1][minus+a[i]] < b[i]){
path[1][minus+a[i]] = i;
s[1][minus+a[i]] = b[i];
}
}
//根据状态方程DP
int ii,jj;
for(int i = 1; i < m; ++i){
for(int j = 0; j < 40*m; ++j){
if(path[i][j] >= 0){
for(int k = 1; k <= n; ++k){
if(s[i+1][j+a[k]] < s[i][j]+b[k]){
for(jj = j, ii = i; ii >= 1; --ii){
if(path[ii][jj] == k) break;
jj -= a[path[ii][jj]];
}
if(ii < 1){
path[i+1][j+a[k]] = k;
s[i+1][j+a[k]] = s[i][j]+b[k];
}
}
}
}
}
}
//计算最小值min
for(int j = 0; j <= 40*m; ++j){
if(s[m][minus+j] >= 0 || s[m][minus-j] >= 0){
if(s[m][minus+j] > s[m][minus-j])
min = minus+j;
else
min = minus-j;
break;
}
}
//沿着路径找节点
int count=0;
for(int j=m,k=min; j >= 1; --j){
c[count++] = path[j][k];
k -= a[c[count-1]];
}
sort(c, c+count);
//打印
printf("Jury #%d\n", ++cn);
printf("Best jury has value %d for prosecution and value %d for defence:\n", (s[m][min]+min-minus)/2, (s[m][min]-min+minus)/2);
for(int i = 0; i < count; ++i)
printf(" %d",c[i]);
printf("\n");
}
return 0;
}