CF1886E I Wanna be the Team Leader (贪心+状压 dp)
CF1886E I Wanna be the Team Leader
贪心+状压 dp
注意到每个项目的程序员水平都要大于等于要求值,那么就相当于限制只与程序员最小值有关。
那么考虑将 \(a\) 序列从小到大排序,那么就有结论:每个项目的程序员都是一段连续的区间。考虑贪心去证这个结论,假如有一段同一个项目的程序员不是连续的,那么将他们拼在一起并将其中其他项目的程序员往后堆,一定不劣,因为不影响当前项目的最小值,还让其他项目的最小值更大。
考虑状压 dp。跟 CF1550E Stringforces 的状态表示一样,预处理也一样。设 \(f_s\) 表示完成项目集合为 \(s\) 的最短前缀。预处理 \(g_{i,j}\) 表示从 \(j\) 位置开始,满足项目 \(i\) 的最小右端点。那么转移就是
\[f_{s|2^i}=\min g_{i,f_s+1}
\]
难点在方案的输出,记录转移点,利用 \(g\) 数组找到每一次选择的区间。
那么就做完了。复杂度 \(O(m2^m)\)。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10, M = 21;
int n, m, lim;
pii a[N], ans[M];
int b[M];
int g[M][N], f[1 << M], pos[1 << M];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> m;
for(int i = 1; i <= n; i++) {
std::cin >> a[i].fi;
a[i].se = i;
}
for(int i = 0; i < m; i++) {
std::cin >> b[i];
}
std::sort(a + 1, a + n + 1, [&](pii a, pii b) {
return a.fi < b.fi;
});
for(int i = 0; i < m; i++) {
g[i][n + 1] = n + 1;
for(int j = n; j >= 1; j--) {
g[i][j] = std::min(g[i][j + 1], std::min(n + 1, j + ((b[i] + a[j].fi - 1) / a[j].fi) - 1));
}
}
lim = (1 << m) - 1;
for(int s = 0; s <= lim; s++) f[s] = n + 1;
f[0] = 0;
for(int s = 0; s < lim; s++) {
if(f[s] > n) continue;
for(int i = 0; i < m; i++) {
if(!(s & (1 << i))) {
if(f[s | (1 << i)] > g[i][f[s] + 1]) {
f[s | (1 << i)] = g[i][f[s] + 1];
pos[s | (1 << i)] = i;
}
}
}
}
if(f[lim] > n) {
std::cout << "NO\n";
return 0;
}
while(lim) {
for(int i = f[lim ^ (1 << pos[lim])] + 1; i <= f[lim]; i++) {
if(g[pos[lim]][i] != g[pos[lim]][i + 1]) {
ans[pos[lim]] = {i, f[lim]};
break;
}
}
lim = lim ^ (1 << pos[lim]);
}
std::cout << "YES\n";
for(int i = 0; i < m; i++) {
std::cout << ans[i].se - ans[i].fi + 1 << " ";
for(int j = ans[i].fi; j <= ans[i].se; j++) {
std::cout << a[j].se << " \n"[j == ans[i].se];
}
}
return 0;
}