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}\),则:

  1. \(\text{cnt} = 0\),则答案为 \(n\)

  2. 否则,答案为 \(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;
}
posted @ 2025-02-13 16:41  zyb_txdy  阅读(29)  评论(0)    收藏  举报