[省选联考 2021 A/B 卷] 卡牌游戏

发布时间 2024-01-07 09:45:49作者: Uuuuuur

看似人类智慧,实则数据结构。

[省选联考 2021 A/B 卷] 卡牌游戏

题目描述

Alice 有 \(n\) 张卡牌,第 \(i\)\(1 \le i \le n\))张卡牌的正面有数字 \(a_i\),背面有数字 \(b_i\),初始时所有卡牌正面朝上。

现在 Alice 可以将不超过 \(m\) 张卡牌翻面,即由正面朝上改为背面朝上。Alice 的目标是让最终朝上的 \(n\) 个数字的极差(最大值与最小值的差)尽量小。请你帮 Alice 算一算极差的最小值是多少。

思路

首先,极差只和当前数列的最大最小值有关,所以我们只有翻最大最小值才会更新答案,由于 \(a\) 的单调性,我们很容易发现一个性质:

翻牌只能从最左边开始翻连续的一段,或从最右边翻连续的一段。

这个很容易吧。

然后开始乱搞,想要枚举左段长度然后算右段,这个其实做不到。

再回到极差这个问题,最原始的做法是固定最小值,找最大值最小

我们分两步考虑。

1.让原本低于最小值的翻上去(左边连续段)

2.原本大于最小值的,不能上翻,如果能下翻且下翻值不低于最小值,需要选(右边连续段)

3.两段之和 \(\le m\)

如果能枚举最小值,然后快速找到这样一种方案,就解决了。

1很好考虑,用二分查找,2的话,发现必须是右边连续段,所以只要找到最右边第一个不合法的。开一棵线段树,维护当前节点 A. 是否存在上翻 B. \(b_i\) 的最小值,那么就很容易在线段树上二分。

void build(int l, int r, int p) {
	if (l == r) {
		up[p] = (b[l] >= a[l]);
		minb[p] = b[l];
		return ;
	}
	int mid = l + r >> 1;
	build(l, mid, ls(p));
	build(mid + 1, r, rs(p));
	pushup(p);
}
int query(int l, int r, int x, int p) {
	if (l == r) return l;
	int mid = l + r >> 1;
	if (a[mid + 1] > x && !up[rs(p)] && minb[rs(p)] >= x) return query(l, mid, x, ls(p));
	else return query(mid + 1, r, x, rs(p)); 
}

这样算出来后和 \(m-1操作个数\) 比对一下,取最优点,然后维护前缀后缀 \(b_i\) 最大值,更新最大值就好了。

for (int i = n; i >= 1; i--) las[i] = max(las[i + 1], b[i]);
	for (int i = 1; i <= n; i++) fro[i] = max(fro[i - 1], b[i]);
	for (int x : value) { //value是a与b的值域
		int t = m;
		if (x < st || x > en) continue; // st,en是保证合法的最小值上下界。
		int maxx = 0;
		int pos = lower_bound(a + 1, a + 1 + n, x) - a - 1;
		if (pos > t) continue;
		maxx = fro[pos];
		t -= pos;
		int f = query(1, n, x, 1);
		f = max(f, n - t);
		maxx = max(maxx, las[f + 1]);
		if (f != pos) maxx = max(maxx, a[f]);
		ans = min(ans, maxx - x);
	}