2025.03.06 CW 模拟赛 C. 列表

C. 列表

原题链接.

思路

我们考虑取数的过程: 第一次一定会取 \(a_{n + 1}\), 然后我们会在 \([1, n]\)\([n + 2, 2n + 1]\) 的区间中选择一个数删除. 如果我们在 \([1, n]\) 中选一个数删除, 那么下一个数会取到 \(a_{n + 2}\), 否则我们会取到 \(a_{n}\).

不难发现 \(a_{n + 1}\)​ 其实是一个分界点, 如果我们在其左边删数, 那么下一次我们选的数就是右边的第一个数; 否则我们在其右边删数, 那么下一次我们选的数就是左边最后一个数.

抽象化一下这个过程, 我们将左右两边分别放在栈里进行维护. 具体的, 我们将左半部分从 1 到 \(n\) 依次压入栈中, 将右半部分从 \(2n + 1\)\(n + 2\) 依次压入栈中. 描述一下上面的操作: 我们每次选择其中一个栈的栈顶加入集合, 再在另一个栈中删除一个数.

我们可以枚举集合最终连续的区间 \([l, r]\), 思考如何判断区间是否合法. 发现我们只关心 \(a_i\) 是否在 \([l, r]\) 内, 所以我们钦定如果 \(a_i \in [l, r]\) 那么 \(a_i = 1\), 否则 \(a_i = 0\).

设两栈分别为 \(A, B\), 我们可以得出一个贪心策略:

  1. 两个栈顶都是 0. 任意选择一个加入集合, 另一个删除即可.
  2. 其中一个是 1. 将 1 加入集合, 另一个 0 删除即可.
  3. 两个都是 1. 将 \(A\) 栈顶的 1 加入集合, 删除 \(B\) 中最靠近栈顶的 0; 再将 \(B\) 栈顶的 1 加入集合, 删除 \(A\) 中最靠近栈顶的 0.

前两个比较显然, 我们考虑第 3 种情况. 对于操作 1, 发现我们会浪费一个 0 来和 1 进行「配对」, 所以我们要将 1 尽量靠近栈顶. 换句话说, 我们要删除最靠近栈顶的 0. 贪心得证.

直接枚举区间是 \(\mathcal{O}(n^2)\) 的. 但是我们发现如果区间 \([l, r]\) 合法, 那么其子区间 \([l - 1, r], [l, r - 1]\) 同样合法, 所以我们可以双指针将枚举做到 \(\mathcal{O}(n)\).

继续发掘性质, 发现我们判断的实质就是判断对于 \(A, B\) 中每一个 1 是否都可以与一个下标大于它的 0 进行配对. 所以区间 \([l, r]\) 有解当且仅当: 对于每个 1 向另一个栈中的下标大于它的连边, 会得到两个二分图, 并且两个二分图均有完美匹配. 从而, \([l, r]\) 合法的充要条件是: 每个后缀中, \(A\) 中 1 的个数不超过 \(B\) 中 0 的个数且 \(B\) 中 1 的个数不超过 \(A\) 中 0 的个数. 发现其中一个条件满足另一个就必定满足, 所以接下来我们只讨论第一个.

\(b_i\) 表示 \([i, n]\) 中 1 的数量减去 \([i + n + 1, 2n + 1]\) 中 0 的数量. 我们在双指针移动的过程中顺便维护并修改前缀即可, 用线段树做到 \(\mathcal{O}(n \log n)\). 不合法当且仅当 \(\exists b_i < 0\).

代码
#include "iostream"

using namespace std;

char buf[1 << 20], *p1, *p2;
#define getchar() (p1 == p2 and (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? 0 : *p1++)

void read() {}
template <class T, class ...T1>
void read(T &x, T1 &...y) {
	x = 0;
	char ch = getchar(); bool f = 0;
	for (; ch < '0' or ch > '9'; ch = getchar()) f |= ch == '-';
	for (; ch >= '0' and ch <= '9'; x = x * 10 + (ch & 15), ch = getchar());
	x = (f ? -x : x);
	read(y...);
}

constexpr int N = 2e5 + 1;

int n, m, pos[N << 1];
int P = 1, mn[N * 3], sum[N * 3];

void build() {
	while (P <= n + 1) P <<= 1;
	for (int i = 1; i <= n; ++i) mn[P + i] = n - i + 1;
	for (int i = P - 1; i; --i) mn[i] = min(mn[i << 1] ? mn[i << 1] : 1e9, mn[i << 1 | 1] ? mn[i << 1 | 1] : 1e9);
}

void update(int l, int r, int k) {
	l += P - 1, r += P + 1;
	while (l ^ 1 ^ r) {
		if (~l & 1) mn[l ^ 1] += k, sum[l ^ 1] += k;
		if (r & 1) mn[r ^ 1] += k, sum[r ^ 1] += k;
		l >>= 1, r >>= 1;
		mn[l] = min(mn[l << 1], mn[l << 1 | 1]) + sum[l];
		mn[r] = min(mn[r << 1], mn[r << 1 | 1]) + sum[r];
	}
	for (l >>= 1; l; l >>= 1) mn[l] = min(mn[l << 1], mn[l << 1 | 1]) + sum[l];
}

void update(int u, int k) {
	int r = pos[u] > n ? pos[u] - n - 1 : n - pos[u] + 1;
	if (r >= 1 and r <= n) update(1, r, k);
}

void init() {
	read(n), m = n << 1 | 1;
	build();
	for (int i = 1, x; i <= m; ++i)
		read(x), pos[x] = i;
}

void calculate() {
	int ans = 0;
	for (int l = 1, r = 0; l <= m; ++l) {
		while (r < m) {
			update(++r, -1);
			if (mn[1] < 0) {
				update(r--, 1);
				break;
			}
		}
		ans = max(ans, r - l + 1);
		update(l, 1);
	}
	printf("%d", ans);
}

void solve() {
	init();
	calculate();
}

int main() {
	solve();
	return 0;
}
posted @   Steven1013  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示