题目大意:有n个人,每个人都有两种评价,第i个人的评价分别为d[i],p[i]。然后给你一个m,让你从中选出m个人,使方案最优,最优方案定义为:令选出m个人的集合为M,则(1)| ∑d[i] - ∑p[i] | 最小,i∈M;(2)在| ∑d[i] - ∑p[i] | 相同的多种方案中,选∑d[i] +∑p[i] 最大的,i∈M。最后输出∑d[i] 和∑p[i]的值,以及被选上人的编号。
思路:这道题是道DP题,但我始终没有想出递推公式,因为我没有想到把| ∑d[i] - ∑p[i] |作为递推参数。由于题目给出等级范围为0~20,因此m最大为20的情况下, ∑d[i] - ∑p[i] 的范围为[-400,400],这就为DP提供了可能。
(1)设置dp(i,j),含义为选出i个人,∑d[i] - ∑p[i] 为j的情况下,∑d[i] +∑p[i] 最大值。则当前dp(i-1,j)存在有效值时,状态转移公式为:dp(i,j+d[x]-p[x])= max(dp(i,j+d[x]-p[x]),dp[i-1]+d[x]+p[x]),max里前一个参数代表不选x,后一个代表选x。其中x满足在dp(i-1,j)方案中,x没有被选过。
(2)设置path(i,j),含义为选出i个人,∑d[i] - ∑p[i] 为j的情况下,最后一个被选上的人的编号,则倒数第二个被选上的人的编号为path(i-1,j - (d[path(i,j)]-p[path(i,j)])),依次类推,则能找到dp(i,j)方案前i个人的所有编号。
(3)由于数组下标不允许出现负数,所以将dp和path的第二个参数都加上20*m,再进行处理。
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,p[205],d[205];
int dp[25][805],path[25][805],ans[25];
bool test(int j,int k,int i)
{
while (j>0 && path[j][k]!=i)
{
k-=(p[path[j][k]]-d[path[j][k]]);
j--;
}
if (j==0)
return true;
return false;
}
int main()
{
int i,j,k,test_cases=1;
while (scanf("%d%d",&n,&m)==2 && (n||m))
{
for (i=1;i<=n;i++)
scanf("%d%d",&p[i],&d[i]);
memset(dp,-1,sizeof(dp));
memset(path,0,sizeof(path));
dp[0][m*20]=0;
for (j=1;j<=m;j++)
for (k=0;k<=m*40;k++)
if (dp[j-1][k]!=-1)
for (i=1;i<=n;i++)
if (dp[j][k+p[i]-d[i]]<dp[j-1][k]+p[i]+d[i] && test(j-1,k,i))
{
dp[j][k+p[i]-d[i]]=dp[j-1][k]+p[i]+d[i];
path[j][k+p[i]-d[i]]=i;
}
for (j=0;dp[m][m*20+j]==-1 && dp[m][m*20-j]==-1;j++);
if (dp[m][m*20+j]>dp[m][m*20-j])
k=m*20+j;
else
k=m*20-j;
printf("Jury #%d\n",test_cases++);
printf("Best jury has value %d for prosecution and value %d for defence:\n",(dp[m][k]+k-m*20)/2,(dp[m][k]-k+m*20)/2);
for (i=0;i<m;i++)
{
ans[i]=path[m-i][k];
k-=(p[path[m-i][k]]-d[path[m-i][k]]);
}
sort(ans,ans+m);
for (i=0;i<m;i++)
printf(" %d",ans[i]);
printf("\n\n");
}
return 0;
}