10 ACwing 280 Jury Compromise 题解
Jury Compromise
题面
在一个遥远的国家,一名嫌疑犯是否有罪需要由陪审团来决定。
陪审团是由法官从公民中挑选的。
法官先随机挑选 N 个人(编号 1,2…,N)作为陪审团的候选人,然后再从这 N 个人中按照下列方法选出 M 人组成陪审团。
首先,参与诉讼的控方和辩方会给所有候选人打分,分值在 0 到 20 之间。
第 i 个人的得分分别记为 p[i] 和 d[i]。
为了公平起见,法官选出的 M 个人必须满足:辩方总分 D 和控方总分 P 的差的绝对值 |D−P| 最小。
如果选择方法不唯一,那么再从中选择辨控双方总分之和 D+P 最大的方案。
求最终的陪审团获得的辩方总分 D、控方总分 P,以及陪审团人选的编号。
注意:若陪审团的人选方案不唯一,则任意输出一组合法方案即可。
\(1 \le N \le 200\)
\(1 \le M \le 20\)
\(0 \le p_i,d_i \le 20\)
题解
这是个典型的物品具有多价值的 01 背包问题,每个人有三个价值:对人数的贡献1,对 \(P\) 的贡献 \(p_i\) ,对 \(D\) 的贡献 \(d_i\)
所以我们可以设 \(f(j,d,p)\) 表示考虑前 \(i\) 个人的情况下,选了 \(j\) 个人,对 \(D\) 的贡献为 \(d\) ,对 \(P\) 的贡献为 \(p\) ,这样一个状态能否达到
答案就是我们在 \(f(M,d,p) = 1\) 里选保证 \(|d - p|\) 最小的情况下,\(d + p\) 最小的
这个做法的时间复杂度为 \(O(NM^5)\) ,大概6e8,包不行的
其实上面的做法并没有充分发挥 01背包 价值和体积的特性,它只解决了存在性问题,但并没有最小化,所以我们可以转化一下,将每个人的价值视为 \(d_i + p_i\) ,体积为 \(d_i - p_i\) ,设 \(f(i,j,k)\) 表示考虑前 \(i\) 个人,已经选了 \(j\) 个人,体积为 \(k\) 的最大价值
答案即为 \(f(n,M,k)\) ,满足 \(|k|\) 最小
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
//如果下标可能为负数的话,那么我们要先定一个基准值,这样的话保证下标合法
const int N = 210, M = 810, base = 405;
int n, m;
int p[N], d[N], ans[N];
int f[N][25][M];
int main () {
int Case = 0;
while (true) {
Case ++;
cin >> n >> m;
if (!n && !m) break;
for (int i = 1; i <= n; i ++) {
cin >> p[i] >> d[i];
}
memset (f, -0x3f, sizeof f);
f[0][0][base] = 0;
for (int i = 1; i <= n; i ++) {
//这里 j 必须从 0 开始,为了继承一开始的状态
for (int j = 0; j <= m; j ++) {
for (int k = 0; k < M; k ++) {
f[i][j][k] = f[i - 1][j][k];
int t = k - (p[i] - d[i]);
if (t < 0 || t >= M) 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 P = 0, D = 0, 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]);
P += p[i];
D += d[i];
i --, j --;
}
}
}
printf ("Jury #%d\n", Case);
printf ("Best jury has value %d for prosecution and value %d for defence:\n", P, D);
if (cnt) reverse (ans + 1, ans + 1 + cnt);
for (int i = 1; i <= cnt; i ++) {
printf (" %d", ans[i]);
}
printf ("\n\n");
}
return 0;
}