CF2106D 官方题解 + 笔记
CF2106D 官方题解
有一种贪心策略。每当你在数组 \(a\) 中看到一朵花,如果它的美丽值不小于你在数组 \(b\) 中接下来必须采摘的花的美丽值,你就会采摘它。如果我们不向 \(a\) 中插入新花,同时依然能用贪心策略采集到 \(b\) 中所有的 \(m\) 朵花,那么答案就是 \(0\)。
现在,考虑一下如果我们在 \(a\) 中插入一个新的美丽值为 \(k\) 的花会怎样:这将允许你“跳过” \(b\) 中的某些花,因为你可以把插入的新花放在 \(a\) 中的任意位置,并在必要时采摘它。因此,与其考虑插入新元素,我们可以将问题重新表述为从 \(b\) 中删除某个元素。一种朴素的思路是,尝试删除 \(b_1\),然后在 \(a\) 上运行贪心算法,对 \(b_2, b_3, ..., b_m\) 继续重复执行。接着,我们保留在成功删除某个花后贪心能完成采集时,对应的最小 \(b_i\)。
#include <bits/stdc++.h>
using namespace std;
int t;
int n, m;
const int INF = 1e9 + 1;
bool greedy(int skip, const vector<int> &a, const vector<int> &b) {
// cout << "开始贪心跳过第 " << skip << " 朵花\n";
int index_b = 0;
if (index_b == skip) {
index_b++;
}
for (auto x : a) {
if (x >= b[index_b]) {
index_b++;
if (index_b == skip) {
index_b++;
}
}
if (index_b >= m) {
// cout << "贪心成功\n";
return true;
}
}
// cout << "贪心失败\n";
return false;
}
void solve() {
cin >> n >> m;
vector<int> a(n), b(m);
for (auto &x : a) {
cin >> x;
}
for (auto &x : b) {
cin >> x;
}
if (greedy(-1, a, b)) {
// 不插入花可以完成
cout << 0;
return;
}
// 不插入花不能完成
int ans = INF;
for (int index_skip = 0; index_skip < m; index_skip++) {
if (greedy(index_skip, a, b)) {
// 跳过下标为 index_skip 的花可以完成
ans = min(ans, b[index_skip]);
}
}
if (ans == INF) {
cout << -1;
return;
}
cout << ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> t;
while (t--) {
solve();
cout << '\n';
}
return 0;
}
复杂度 \(O(nm)\) 会 TLE。
优化
现在需要把这部分优化掉:
for (int index_skip = 0; index_skip < m; index_skip++) {
if (greedy(index_skip, a, b)) {
// 跳过下标为 index_skip 的花可以完成
ans = min(ans, b[index_skip]);
}
}
有没有一种办法,可以线性地判断能否删除花 \(b_i\)?
如果花 \(b_i\) 可以被删除,意味着 \(b_i\) 之前的所有花和 \(b_i\) 之后的所有花都可以被 \(a\) 序列满足。对每一朵花 \(b_i\),找到最短的满足 \(b_1, b_2, ..., b_i\) 的 \(a_1, a_2, ..., a_{p_i}\),最短的满足 \(b_i, b_{i+1}, ..., b_m\) 的 \(a_{s_i}, a_{s_i+1}, ..., a_n\) 。如果花 \(b_i\) 可以被删除,那么花 \(b_{i-1}\) 的前缀 \(a\) 序列一定和花 \(b_{i+1}\) 的后缀 \(a\) 序列没有重叠,也就是满足 \(p_{i-1}\ <\ s_{i+1}\)。如果重叠,说明去掉花 \(b_i\) 后,剩余的部分仍不能被 \(a\) 序列满足,花 \(b_i\) 无法被删除。
用双指针可以线性地预处理前缀和后缀。