Codeforces 1316E Team Building

Description

你需要组建一支排球队。为了组织一支排球队,你需要为队伍里的 $p$ 个不同的位置,从 $n$ 个人中选出 $p$ 个人,且每个位置上都恰好有一个人。另外还需要从剩下的人中选出恰好 $k$ 个人作为观众。

对于第 $i$ 个人,已知他作为观众时能为队伍增加 $a_i$ 点力量,还有他在队伍的第 $j$ 个位置上时能为队伍增加 $s_{i,j}$ 点力量。

请问这只排球队力量的最大值是多少?

Solution

看到 $p$ 很小,考虑状态压缩 DP。

这里先把每个人按照 $a_i$ 降序排序。

设想,对于一个状态 $f_{i, s}$,$s$ 是一个 $p$ 位二进制数,表示队伍的选择情况,当前这个 $i$ 会不会被选进观众呢?

这里用到一个贪心,如果 $i le operatorname{CountBit}(s) + k$,那么,我们就会把 $i$ 选到观众里面去。

这很好理解,原本应该是选前 $k$ 个当观众,但是有 $operatorname{CountBit}(s)$ 个已经被选到队伍里面去了,所以我们把限制往后移这么多位。

接下来,考虑把 $i$ 弄成队员。我们枚举一个 $s$ 包含的位置 $t$,试图把 $i$ 放进去,那么就要用 $f_{i-1, s oplus 2^t}$ 来更新 $f_{i, s}$。

这题作为状态压缩 DP 还是比较好实现的,时间复杂度 $mathcal O(n cdot 2^p)$,具体细节可以看代码。代码中用 $j$ 代替了 $s$。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5, P = 130;
struct people
{
    LL a, s[7];
    bool operator < (const people &oth) const { return a > oth.a; }
} peo[N];
int n, p, k;
inline int CountBit(int x)
{
    int res = 0;
    while(x) res++, x &= x - 1;
    return res;
} 
LL f[N][P];
int main()
{
    scanf("%d %d %d", &n, &p, &k);
    for(int i = 1; i <= n; i++) scanf("%lld", &peo[i].a);
    for(int i = 1; i <= n; i++)
        for(int j = 0; j < p; j++)
            scanf("%lld", &peo[i].s[j]);
    sort(peo + 1, peo + n + 1);
    int full = 1 << p;
    memset(f, -0x3f, sizeof f);
    f[0][0] = 0;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j < full; j++)
        {
            int cnt = CountBit(j);
            f[i][j] = f[i - 1][j];
            if(i <= cnt + k) f[i][j] = max(f[i][j], f[i - 1][j] + peo[i].a);
            for(int t = 0; t < p; t++) if(j >> t & 1)
                f[i][j] = max(f[i][j], f[i - 1][j ^ (1 << t)] + peo[i].s[t]);
        }
    }
    printf("%lld
", f[n][full - 1]);
    return 0;
}
原文地址:https://www.cnblogs.com/syksykCCC/p/CF1316E.html