UVA323题解
这题可以背包动态规划来做
是一个多个维度的 $01$ 背包。把 n 个候选人看作 n 个物品,
维护 $3$ 个维度
$f[j][k]$ 表示前 $i$ 个人中选出 $j$ 个,总分差为 $k$ 时,俩总分和的最大值
转移方程 :
-
不选第 $i$ 个,$f[j][k]$
-
选第 $i$ 个,$f[j - 1][k - (a[i] - b[i])] + a[i] + b[i]$
所以,$f[j][k] = \max (f[j][k], f[j - 1][k - (a[i] - b[i])) + a[i] + b[i])$
因为总分差有可能是负数,每个 k 加 $400$,那么 k 的范围是 $1,...,800$
初值:$f[0][0] = 0$,其他为 $-\infty$
目标:找到一个状态 $f[j][k]$,使得$\mid k - 400\mid$最小的情况下,$f[j][k]$ 最大
注意:因为每个人只能选一次,故 $j$ 维要倒序枚举
此题要求具体方案
设 $last[i][j][k]$ 代表当第 i 维度时,$f[j][k]$ 是从哪个维度转移
注意:维度不能省!!!
求出最优解后,可以沿着 last 数组找到方案:
设 $use = last[i][j][k]$,从状态 $(i, j, k)$ 到状态 $(use - 1, j - 1, use - (a[i] - b[i])))$。最后到$j = 0$ 结束
所有的 $use$ 就是人选。
具体实现可以参考程序。
#include <iostream>
#include <cstring>
using namespace std;
int n, m, cnt, ans1, ans2, a[210], b[210], f[30][810], ans[30], last[210][30][810];
void find (int i, int j, int k) {
if (j == 0) return ;
int use = last[i][j][k];
find (use - 1, j - 1, k - (a[use] - b[use]));
ans[++ cnt] = use; ans1 += a[use]; ans2 += b[use];
}
int main() {
int T = 0;
while (cin >> n >> m && n) {
T ++;
for (int i = 1; i <= n; ++i) cin >> a[i] >> b[i];
memset (f, -0x3f, sizeof (f)); cnt = 0; ans1 = 0; ans2 = 0;
f[0][400] = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= m; ++j) {
for (int k = 0; k <= 800; ++k) last[i][j][k] = last[i - 1][j][k];
}
for (int j = m; j; --j) {
for (int k = 0; k <= 800; ++k) {
int res = k - (a[i] - b[i]);
if (res < 0 || res > 800) continue;
if (f[j][k] < f[j - 1][res] + a[i] + b[i]) {
f[j][k] = f[j - 1][res] + a[i] + b[i];
last[i][j][k] = i;
}
}
}
}
int sum = 0;
for (int i = 0; i <= 400; ++i) {
if (f[m][400 + i] >= 0 && f[m][400 + i] >= f[m][400 - i]) {
sum = 400 + i; break;
}
if (f[m][400 - i] >= 0) {
sum = 400 - i; break;
}
}
find (n, m, sum);
cout << "Jury #" << T << endl;
cout << "Best jury has value " << ans1 << " for prosecution and value " << ans2 << " for defence:\n";
for (int i = 1; i <= cnt; ++i) cout << " " << ans[i];
cout << "\n\n";
}
return 0;
}