题目链接:
https://www.acwing.com/problem/content/description/282/
题意:
有n个人,每个人有p,d两种属性,现在要从中选出m个人,使得p属性之和与d属性之和差的绝对值最小,若有多个方案有相同的最小绝对值差,取p+d最大的方案,并输出方案。(1≤N≤200,1≤M≤20,0≤p[i],d[i]≤20)
思路:
我们可以把每个人p和d的差值提高20的偏移量,这样差值就大于0了,然后01背包凑数的过程,存下每种数的p,d属性,一个数可以由多个方式凑出来时,取p+d最大的情况,最后要找到可以被凑数来且最接近m*20的数。求具体方案就倒推一遍。
代码:
#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
#include<stdio.h>
using namespace std;
struct ac{
int p,d,w,id;
}a[210];
struct de{
int p,d,f;
}f[210][21][810];
vector<int> v;
int main(){
int n,m,cas=1;
while(1){
cin>>n>>m;
if(n==0&&m==0) break;
for(int i=1;i<=n;++i){
cin>>a[i].p>>a[i].d;
a[i].w=a[i].p-a[i].d+20,a[i].id=i;
}
int gal=m*20;
v.clear();
memset(f,0,sizeof f);
f[0][0][0].f=1;f[0][0][0].p=f[0][0][0].d=0;
for(int i=1;i<=n;++i){
int w=a[i].w,p=a[i].p,d=a[i].d;
memcpy(f[i],f[i-1],sizeof f[i]);
for(int j=m;j>=1;--j){
for(int k=800;k>=w;--k){
if(f[i-1][j-1][k-w].f&&!f[i][j][k].f) {
f[i][j][k].f=1;
f[i][j][k].p=f[i-1][j-1][k-w].p+p;
f[i][j][k].d=f[i-1][j-1][k-w].d+d;
}
else if(f[i-1][j-1][k-w].f&&f[i][j][k].f &&f[i-1][j-1][k-w].p+f[i-1][j-1][k-w].d+p+d>f[i][j][k].p+f[i][j][k].d){
f[i][j][k].p=f[i-1][j-1][k-w].p+p;
f[i][j][k].d=f[i-1][j-1][k-w].d+d;
}
}
}
}
int mn=1000000,ap=0,ad=0;
for(int i=800;i>=0;--i) {
if(f[n][m][i].f&&( abs(i-gal)<abs(mn-gal)||( abs(i-gal)==abs(mn-gal) && f[n][m][i].p+f[n][m][i].d>ap+ad ) ) ){
mn=i;
ap=f[n][m][i].p;
ad=f[n][m][i].d;
}
}
printf("Jury #%d
",cas++);
printf("Best jury has value %d for prosecution and value %d for defence:
",ap,ad);
int i=n,j=m;
while((ap||ad)&&i){
for(int k=0;k<=800;++k){
if(f[i-1][j-1][k].f&&ap==f[i-1][j-1][k].p+a[i].p && ad==f[i-1][j-1][k].d+a[i].d){
ap-=a[i].p,ad-=a[i].d;
v.push_back(i);
--j;
break;
}
}
--i;
}
reverse(v.begin(),v.end());
for(int i=0;i<v.size();++i) printf(" %d",v[i]);
printf("
");
}
return 0;
}