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; }