题目链接:https://www.acwing.com/problem/content/description/282/
题目给出两个序列p,d 要求寻找一个方案,使得p-d的绝对值最小,在绝对值相同的情况下p+d的值尽量大,通过dp保存状态:前i个人中选择了j个,并且差为k时的p+d的最大值。转移方式就是划分集合,判断是否选择第i个人,最后通过回溯即可判断是否选择一个人。这里的差值在[-400,400]之间,所以可以加上偏移400使得下标为正。最初的状态只有f[0,0,400]=0,表示没开始选择的时候,差值为0的最大值为0。
代码:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxn = 210,maxm = 810,base=400; int n,m; int p[maxn],d[maxn]; int f[maxn][21][maxm]; int ans[maxn]; int main(){ int T=1; while(scanf("%d%d",&n,&m),n||m){ for(int i=1;i<=n;i++)scanf("%d%d",&p[i],&d[i]); memset(f,-0x3f,sizeof f); f[0][0][base]=0;//起初只有一个合法状态 for(int i=1;i<=n;i++) for(int j=0;j<=m;j++) for(int k=0;k<maxm;k++){ f[i][j][k]=f[i-1][j][k]; int t=k-(p[i]-d[i]); if(t<0 || t>=maxm)continue; if(j<1)continue; f[i][j][k]=max(f[i][j][k],f[i-1][j-1][t]+p[i]+d[i]); } int v=0;//判断差值最小的方案 while(f[n][m][base-v]<0 && f[n][m][base+v]<0)v++; if(f[n][m][base-v]>f[n][m][base+v])v=base-v; else v=base+v; int cnt = 0; int i=n,j=m,k=v; while(j){//状态回溯 if(f[i][j][k]==f[i-1][j][k])i--; else{ ans[cnt++]=i; k-=(p[i]-d[i]); i--,j--; } } int sp=0,sd=0; for(int i=0;i<cnt;i++) sp+=p[ans[i]],sd+=d[ans[i]]; printf("Jury #%d\n",T++); printf("Best jury has value %d for prosecution and value %d for defence:\n",sp,sd); for(int i=0;i<cnt;i++)printf(" %d",ans[i]); cout<<endl<<endl; } return 0; }