CodeForces 1886E I Wanna be the Team Leader
把题意抽象成,给你长为 \(n\) 的序列 \(a\) 和长为 \(m\) 的序列 \(b\),初始有 \(m\) 个空集合(可重集),\(a\) 中的每个元素至多被分到 \(m\) 个集合中的一个。要求最后第 \(i\) 个集合 \(T_i\) 不为空,且 \(\forall x \in T_i, x \ge \frac{b_i}{|T|}\)。要求构造方案或报告无解。
先把 \(a\) 从大到小排序。发现最后每个集合分到的一定是在排序后序列的一段区间。因为 \(11121222\) 显然比 \(11112222\) 劣。
然后考虑一个从前往后分配集合元素的可行性 dp。设 \(f_{i, S}\) 为当前 \(a\) 中 \([1, i]\) 的元素被分配完了,\(S\) 为已经考虑完的集合的编号集合为 \(S\)。转移枚举下一段的右端点 \(j\),枚举 \([i + 1, j]\) 被分到第 \(k\) 个集合,有 \(f_{j, S \cup \{k\}} \gets f_{i, S}\)。要求 \(a_j \ge \frac{b_k}{j - i}\)。
发现这个 dp 太蠢了。考虑直接把第一维塞进 dp 值中。重新设 \(f_S\) 为已经考虑完的集合的编号集合为 \(S\),若 \([1, i]\) 被分配完了,\(i\) 的最小值。预处理出 \(g_{i, j}\) 表示最小的 \(k\) 使得 \(a_k \ge \frac{b_i}{k - j}\),转移考虑枚举下一个集合是第 \(k\) 个,有 \(f_{S \cup \{k\}} \gets g_{k, f_S + 1}\)。
求 \(g\) 可以双指针,因为 \(g_{i, j}\) 随 \(j\) 增大而不降。所以整个的复杂度就是 \(O((n + 2^m) m + n \log n)\)。
code
// Problem: E. I Wanna be the Team Leader
// Contest: Codeforces - Educational Codeforces Round 156 (Rated for Div. 2)
// URL: https://codeforces.com/contest/1886/problem/E
// Memory Limit: 512 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define pb emplace_back
#define fst first
#define scd second
#define mkp make_pair
#define mems(a, x) memset((a), (x), sizeof(a))
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;
const int maxn = 200100;
const int maxm = (1 << 20) + 50;
ll n, m, b[25], g[25][maxn], f[maxm], p[maxm];
vector<int> ans[25];
struct node {
ll x, i;
} a[maxn];
void solve() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%lld", &a[i].x);
a[i].i = i;
}
sort(a + 1, a + n + 1, [&](const node &a, const node &b) {
return a.x > b.x;
});
for (int i = 1; i <= m; ++i) {
scanf("%lld", &b[i]);
}
for (int i = 1; i <= m; ++i) {
for (int j = 1, k = 1; j <= n; ++j) {
k = max(k, j);
while (k <= n && a[k].x * (k - j + 1) < b[i]) {
++k;
}
g[i][j] = k;
}
}
mems(f, 0x3f);
f[0] = 0;
for (int S = 0; S < (1 << m); ++S) {
if (f[S] >= n) {
continue;
}
for (int i = 1; i <= m; ++i) {
if (S & (1 << (i - 1))) {
continue;
}
int T = S | (1 << (i - 1));
if (f[T] > g[i][f[S] + 1]) {
f[T] = g[i][f[S] + 1];
p[T] = i;
}
}
}
if (f[(1 << m) - 1] > n) {
puts("NO");
return;
}
puts("YES");
int S = (1 << m) - 1;
while (S) {
int T = S ^ (1 << (p[S] - 1));
for (int i = f[T] + 1; i <= f[S]; ++i) {
ans[p[S]].pb(a[i].i);
}
S = T;
}
for (int i = 1; i <= m; ++i) {
printf("%d ", (int)ans[i].size());
for (int x : ans[i]) {
printf("%d ", x);
}
putchar('\n');
}
}
int main() {
int T = 1;
// scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}