[ARC106E] Medals 题解
题意:
你有 \(n\) 个朋友,他们会来你家玩,第 \(i\) 个人 \(1...A_i\) 天来玩,然后 \(A_i+1...2A_i\) 天不来,然后 \(2A_i+1...3A_i\)
又会来,以此类推
每天你会选一个来玩的人,给他颁个奖,如果没人来玩,你就不颁奖。
你要给每个人都颁 \(K\) 个奖,问至少需要多少天。
\(n \le 18, k, a_i \le 10^5\)
思路:
好题一枚。
首先答案明显具有单调性,考虑二分。考虑到一个人最多花 \(2k\) 天,所以总共不超过 \(2nk\) 天。
二分后会发现这是一个网络流,左部点每个有 \(k\) 流量,要求满流。
但是这样很不好做,发现 \(n\) 很小,这明显是 Hall 定理的题目。
枚举左部点子集,要快速计算右边邻域大小,考虑到这是一个并的关系,不好做,转化成这个子集的补集有多少右部点其邻域全在这个补集中。
假设 \(B_S\) 表示有多少个右部点邻域恰好为 \(S\),则我们要求的就是 \(A_S = \sum_{S \sube T}B_T\)。
这就是经典的 SOSDP 的形式,我们用 SOSDP 即可在 \(O(n2^n)\) 内解决这个问题。
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 22;
const int MS = (1 << 18) + 5;
const int M = 1e5 + 5;
int n, k, a[N] = {0};
int cnt[M * N * 2] = {0};
int f[MS] = {0}, pc[MS] = {0};
void show(int x) {
for (int j = 0; j < n; j++)
cout << (x >> j & 1);
cout << " ";
}
bool chk(int x) {
for (int i = 1; i <= x; i++)
cnt[i] = 0;
for (int i = 0; i < (1 << n); i++)
f[i] = 0;
for (int i = 0; i < n; i++) {
int cur = 1, d = 1;
while (cur <= x) {
cnt[cur] |= (1 << i);
d++, cur++;
if (d > a[i])
d = 1, cur += a[i];
}
}
for (int i = 1; i <= x; i++)
f[cnt[i]]++;//, show(cnt[i]), cout << endl;
for (int i = 0; i < n; i++)
for (int j = 0; j < (1 << n); j++)
if (!(j >> i & 1))
f[j | (1 << i)] += f[j];
for (int i = 0; i < (1 << n); i++)
if (pc[i] * k > x - f[(1 << n) - 1 - i])
return false;
return true;
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < (1 << n); i++)
for (int j = 0; j < n; j++)
pc[i] += (i >> j & 1);
int l = 0, r = 2 * n * k;
while (l + 1 < r) {
int mid = (l + r) / 2;
if (chk(mid))
r = mid;
else
l = mid;
}
cout << r << endl;
return 0;
}