題目大意:有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;
}