CF2067E
题意
对于一个长度为 \(n\) 的正整数序列 \(a\),定义它为「有魔力的」,当且仅当:\(\forall 1 \le i < n\),\(\min(a_1, ... , a_i) \ge \text{mex}(a_{i + 1}, ... , a_n)\)。
现在有多组询问,每次给定一个正整数 \(n\) 和一个长度为 \(n\) 的序列 \(a\),问 \(a\) 的最长「有魔力的」子序列有多长。
数据保证 \(1 \le \sum n \le 2 \times 10^5\),\(0 \le a_i \le 10^9\)。
分析
被这题打爆了呜呜,可能写不出一些逻辑性的思考了。
首先我们有一个观察是:对于序列 \(a\),如果这个序列中不存在 \(0\),那么它必定是「有魔力的」。这个的证明比较显然:此时 \(\forall 1 \le i < n\),\(\text{mex}(a_{i + 1}, ... , a_n) = 0\),而 \(\min(a_1, ... , a_i)\) 必定是自然数,故该观察成立。
进一步地,若序列中有两个及以上的 \(0\) 存在,那么它必定不是「有魔力的」。证明只需要取两个 \(0\) 中间的一个位置作为 \(i\) 即可。
我们发现这两个观察,可以对原序列的最长「有魔力的」子序列进行一个很大的限制。具体地,设原序列中 \(0\) 的个数为 \(\text{cnt}\),则:
-
若 \(\text{cnt} = 0\),则答案为 \(n\)。
-
否则,答案为 \(n - \text{cnt}\) 或 \(n - \text{cnt} + 1\)。
接下来只需要讨论第二种情况即可。一个显然的想法是枚举 \(a\) 的所有不为 \(0\) 的元素与一个 \(0\) 组成的子序列并依次判断,复杂度为 \(O(n^2)\),显然过不了。并且每次判断都需要处理前缀 \(\min\) 和后缀 \(\text{mex}\),并不是很好快速转移。
但是还有一个性质是:对于一个不含 \(0\) 的序列,若在下标为 \(i\) 的位置插入一个 \(0\) 后,序列仍然是「有魔力的」,则在下标为 \(j\)(\(j < i\))的地方插入一个 \(0\),序列必定也是「有魔力的」。证明只需考虑在 \(j\) 插入 \(0\) 后所有可能不合法的位置必定在 \(i\) 的方案中出现过即可。
于是在原序列 \(a\) 中存在 \(0\) 时,我们只需要检查 \(a\) 的所有不为 \(0\) 的值加上 \(a\) 中最左边的 \(0\) 组成的序列是否是「有魔力的」即可。
-
若它是有魔力的,则答案为 \(n - \text{cnt} + 1\)。
-
否则,答案为 \(n - \text{cnt}\)。
这个检查需要统计前缀 \(\min\) 和后缀 \(\text{mex}\),而这两个东西的 \(O(n)\) 求法都是简单的。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e5 + 5;
int T, n;
int a[N], buc[N], pre[N], suf[N];
void Solve() {
cin >> n; int cnt = 0;
for (int i = 1; i <= n; ++i) { cin >> a[i]; cnt += (a[i] == 0); }
if (cnt == 0) { cout << n << endl; }
else {
int m = 0, res = 0; bool flag = true;
for (int i = 1; i <= n; ++i) {
if (a[i] != 0) { a[++m] = a[i]; }
else if (flag) { a[++m] = a[i]; flag = false; }
}
for (int i = 1; i <= m; ++i) pre[i] = (i == 1 ? a[i] : min(pre[i - 1], a[i]));
for (int i = m; i > 0; --i) {
if (a[i] <= m) ++buc[a[i]];
while (buc[res]) ++res;
suf[i] = res;
}
for (int i = 1; i <= m; ++i) if (a[i] <= m) --buc[a[i]]; // clear
for (int i = 1; i < m; ++i) if (pre[i] < suf[i + 1]) { cout << n - cnt << endl; return ; }
cout << n - cnt + 1 << endl;
}
}
signed main() {
cin >> T;
while (T--) Solve();
return 0;
}

浙公网安备 33010602011771号