题目
在 n n n人中选择 m m m人,一方得分为 a [ i ] a[i] a[i],另一方得分 b [ i ] b[i] b[i],使 ∑ ( s o m e − o f − t h e m ) a b s ( a [ x ] − b [ x ] ) \sum(some-of-them)abs(a[x]-b[x]) ∑(some−of−them)abs(a[x]−b[x])最小,且 ∑ ( s o m e − o f − t h e m ) a [ x ] + b [ x ] \sum(some-of-them)a[x]+b[x] ∑(some−of−them)a[x]+b[x]最大,并输出方案
分析
可以用一种类似于01背包的思想,f[i][j]表示前i个候选人此时辩方+控方为j,辩方-控方的最小值
f
[
i
]
[
j
+
s
u
b
[
k
]
]
=
f
[
i
−
1
]
[
j
]
+
a
d
d
[
k
]
;
f[i][j+sub[k]]=f[i-1][j]+add[k];
f[i][j+sub[k]]=f[i−1][j]+add[k];
路径的时候
p
a
t
h
[
i
]
[
j
+
s
u
b
[
k
]
]
=
k
;
path[i][j+sub[k]]=k;
path[i][j+sub[k]]=k;
动态规划好像可以用滚动数组(但是我懒)
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
int n,m,f[31][801],path[31][801],add[801],sub[801],cnt;
int in(){
int ans=0; char c=getchar();
while (c<48||c>57) c=getchar();
while (c>47&&c<58) ans=ans*10+c-48,c=getchar();
return ans;
}
bool check(int x,int y,int z){
while (x&&path[x][y]!=z){//路径可行
y-=sub[path[x][y]];
x--;
}
return !x;
}
int main(){
while (1){
n=in(); m=in(); if (!n&&!m) return 0;
memset(f,-2,sizeof(f)); memset(path,0,sizeof(path)); f[0][m*20]=0;
for (register int i=1;i<=n;i++){
int a=in(); int b=in();
add[i]=a+b; sub[i]=a-b;
}
for (register int i=1;i<=m;i++)
for (register int j=0;j<=m*40;j++)
if (f[i-1][j]>=0)//有方案
for (register int k=1;k<=n;k++)
if (f[i][j+sub[k]]<f[i-1][j]+add[k]&&check(i-1,j,k)){//最小且路径符合
f[i][j+sub[k]]=f[i-1][j]+add[k];
path[i][j+sub[k]]=k;
}
printf("Jury #%d \n",++cnt);
int k1,ans[m+1];
for (k1=0;k1<=m*20&&f[m][m*20+k1]<0&&f[m][m*20-k1]<0;k1++);//找出最小的答案
int answer=f[m][m*20+k1]>f[m][m*20-k1]?m*20+k1:m*20-k1;
printf("Best jury has value %d for prosecution and value %d for defence:\n",(answer+f[m][answer]-m*20)>>1,(f[m][answer]-answer+m*20)>>1);
for (register int i=1,j=m,k=answer;i<=m;i++){
ans[i]=path[j][k];//记录得分
k-=sub[path[j][k]];
j--;
}
std::stable_sort(ans+1,ans+1+m);//快排
for (register int i=1;i<=m;i++) printf(" %d",ans[i]);
putchar('\n'); putchar('\n');
}
}