AtCoder ARC106 E Hall 定理 + 二分 + 容斥原理 + 高维前后缀和

205 篇文章 4 订阅
164 篇文章 0 订阅
题意

传送门 AtCoder ARC106 E Medals

题解

问题可以转化为每一天与职员之间的匹配问题,思路与 AtCoder ABC320 G Slot Strategy 2 (Hard) 类似。但二分图规模过大,直接求解最大匹配显然难以胜任。

根据 Hall 定理,若二分图一侧点集 S S S 都能被匹配的充要条件是,对于 S S S 的任一子集 A A A,与其存在连边的二分图另一侧节点的数量要大于等于 ∣ A ∣ \vert A\vert A。二分答案,上界为 2 ⋅ n ⋅ k 2\cdot n \cdot k 2nk,因为每一个职工的休息日之前总是对应唯一的工作日。预处理出每一天可以选择的用户集合。二分确定工作时长之后,可以统计出对于每一个确定的用户集合 s s s,其恰好能被选择的天数 f ( s ) f(s) f(s)。令 A i A_i Ai 代表用户 i i i 被选择的日子的集合。那么对 f f f 做高维后缀和,就能求出 A i A_i Ai 的交集的规模,为了求出 A A A 的并集的规模,使用容斥原理,求高维前缀和即可。总时间复杂度 O ( n 2 k + n 2 n log ⁡ ( n k ) ) O\Big(n^2k+n2^n\log(nk)\Big) O(n2k+n2nlog(nk))

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, k;
    cin >> n >> k;
    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }
    int lim = 2 * n * k;
    vector<int> state(lim);
    for (int i = 0; i < lim; ++i) {
        int x = 0;
        for (int j = 0; j < n; ++j) {
            if (i % (2 * a[j]) < a[j]) {
                x |= 1 << j;
            }
        }
        state[i] = x;
    }
    auto judge = [&](int d) {
        vector<int> dp(1 << n);
        for (int i = 0; i < d; ++i) {
            if (state[i] > 0) {
                dp[state[i]] += 1;
            }
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 1; j < 1 << n; ++j) {
                if (~j >> i & 1) {
                    dp[j] += dp[j ^ 1 << i];
                }
            }
        }
        for (int i = 0; i < 1 << n; ++i) {
            if (~__builtin_popcount(i) & 1) {
                dp[i] *= -1;
            }
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < 1 << n; ++j) {
                if (j >> i & 1) {
                    dp[j] += dp[j ^ 1 << i];
                }
            }
        }
        for (int i = 1; i < 1 << n; ++i) {
            if (dp[i] < __builtin_popcount(i) * k) {
                return false;
            }
        }
        return true;
    };
    int lb = 0, ub = lim;
    while (ub - lb > 1) {
        int mid = (lb + ub) / 2;
        if (judge(mid)) {
            ub = mid;
        } else {
            lb = mid;
        }
    }
    cout << ub << '\n';

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值