Kick Start 19G Shifts

链接

题目大意:有甲乙两人工作,工作分为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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值