[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;
}
posted @ 2024-05-30 21:05  rlc202204  阅读(8)  评论(0编辑  收藏  举报