UVA323题解

这题可以背包动态规划来做

是一个多个维度的 $01$ 背包。把 n 个候选人看作 n 个物品,

维护 $3$ 个维度

$f[j][k]$ 表示前 $i$ 个人中选出 $j$ 个,总分差为 $k$ 时,俩总分和的最大值

转移方程 :

  1. 不选第 $i$ 个,$f[j][k]$

  2. 选第 $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;
}
posted @ 2021-05-16 17:37  wangzhongyuan  阅读(3)  评论(0编辑  收藏  举报  来源