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;
}