题目大意:有甲乙两人工作,工作分为N个班次shift(编号0到N-1。每个班次至少有一个人要工作,对于班次i,如果甲工作,他能获得Ai的幸福点;如果乙工作,Bi。求有多少种班次分配方案使得两人幸福点都>=H。
- 0 ≤ H ≤ 109.
- 0 ≤ Ai ≤ 109.
- 0 ≤ Bi ≤ 109.
- 1 ≤ N ≤ 20
解法1
如果枚举每一种方案,每个班次可以分为甲,乙,甲乙三种情况,复杂度为O(3N),TLE
解法2
枚举甲的分配方案,对于每个方案,总结果加上符合要求的乙的方案的个数。
单独一个人的方案用N位的掩码表示,表示在哪些班次工作。如果bit i为1,说明参加shift i。
- sumA[mask] 甲采取mask获得的幸福点
- sumB[mask] 乙采取mask获得的幸福点
可以通过两个DFS得到,时间O(2N)
当甲采取a方案,如果sumA[a] < H,显然总结果+ 0,所以我们只处理sumA[a] >= H。设乙采取b方案,a∪b需要为全集full=2N-1,所以b的补集c是a的子集,sumB[c] = sumB[full] - sumB[b] <= sumB[full] - H = 固定值rem。接下来,我们只需要找到能满足sumB[c] <= rem的a的子集c的个数。使用SoS DP将复杂度降到O(N2N)。参考
#include <iostream>
#include <vector>
#include <unordered_map>
#include <string>
#include <algorithm>
#include <numeric>
#define ALL(s) s.begin(), s.end()
using namespace std;
using ll = long long;
class Solution {
public:
int N; // [1,20]
int H; // [0, 1e9]
vector<int> A, B;
vector<ll> sumA, sumB;
ll rem;
vector<vector<ll>> tbl;
void dfs(int i, int mask, ll sum, vector<int>& vi, vector<ll>& vll) {
if(i == N) {
vll[mask] = sum;
return;
}
dfs(i+1, mask | (1 << i), sum + vi[i], vi, vll);
dfs(i+1, mask, sum, vi, vll);
}
// x的个数,x是mask子集,且高于bit i的部分x与mask相同,且sumB[x] <= rem
ll dp(int mask, int i) {
if(i == -1) {
return sumB[mask] <= rem ? 1 : 0;
}
ll& res = tbl[mask][i];
if(res != -1) return res;
if(mask & (1 << i)) {
res = dp(mask, i-1) + dp(mask ^ (1 << i), i-1);
} else {
res = dp(mask, i-1);
}
return res;
}
void solve() {
cin >> N >> H;
A.resize(N); B.resize(N);
for(int i = 0; i < N; i++) cin >> A[i];
for(int i = 0; i < N; i++) cin >> B[i];
sumA.resize(1 << N);
sumB.resize(1 << N);
dfs(0, 0, 0, A, sumA);
dfs(0, 0, 0, B, sumB);
rem = sumB[(1<<N) - 1] - H;
tbl = vector<vector<ll>>(1<<N, vector<ll>(N, -1));
ll res = 0;
for(int mask = 0; mask < (1<<N); mask++) {
if(sumA[mask] >= H) res += dp(mask, N-1);
}
cout << res << endl;
}
};
int main() {
int T;
cin >> T;
auto sln = new Solution();
for (int i = 1; i <= T; i++)
{
cout << "Case #" << i << ": ";
sln->solve();
}
return 0;
}