题解:P16996 【MX-S15-T2】「DLESS-5」宇宙射线

挺有意思的题,思路比较自然。

设相反的比较出现在第 \(n+1-m\) 轮冒泡中,这一轮冒泡后 \(a_m=x\),由于前后的轮次中比较是正确的,若 \(x<m\),则最终 \(a\) 一定形如

\[1,\cdots,m,x,m+1,\cdots,n \]

\(x=m\),则最终 \(a\) 是有序的。

因此问题可以转化为对于每个 \(m\),求出有多少种不同的不为 \(m\)\(x\)

设经过 \(n-m\) 轮排序后的排列为 \(b\),我们可以枚举 \(i\),表示 \(b_i\)\(b_{i+1}\) 的比较是错误的,则

\[x=\max\left(\min(pre_i,a_{i+1}),suf_{i+2}\right) \]

其中 \(pre,suf\) 分别表示 \(b_{1\sim m}\) 的前缀最大值和后缀最大值。

直接做就是 \(\mathcal{O}(n^2)\) 的,过程中需要记录是否存在恰为 \(m\)\(x\)

考虑优化,我们需要更简洁的方式来刻画一个值 \(x\) 能否被冒泡到 \(a_m\) 上。

观察到,对于 \(x\leq m-2\),若存在 \(x+1\leq v\leq m\) 在本轮冒泡前出现在 \(x\) 的右侧,则 \(x\) 要冒泡到 \(a_m\) 处至少要进行 \(2\) 次错误的比较;反过来,若 \(x+1\sim m\) 在本轮冒泡前都出现在 \(x\) 的左侧,则显然可以通过 \(1\) 次错误的比较将其冒泡到 \(a_m\)。这样我们就得到了一个充要条件。

尝试进一步刻画这个条件。我们知道若某一轮冒泡开始前,\(x\) 的前面有比它大的数,则这一轮中 \(x\) 会恰好向左跨过一个比它大的数。而 \(x+1\sim m\) 在冒泡开始前都在 \(x\) 左侧,因此前面 \(n-m\) 轮中,\(x\) 恰好依次向左跨过了 \(n,\cdots,m+1\)。于是设 \(pos_x\)\(x\) 在原排列 \(a\) 中出现的位置,我们可以得到很简洁的条件:

\[pos_x>\max_{i=x+1}^npos_i \]

显然 \(x=m-1\) 一定可以被冒泡到 \(a_m\) 上,额外计算贡献即可。

\(mx_i=\max\limits_{j=i}^npos_j\),那么总的贡献就是

\[\sum_{m=2}^n\left(1+\sum_{x=1}^{m-2}[pos_x>mx_{x+1}]\right) \]

容易 \(\mathcal{O}(n)\) 计算。

注意我们还需要判断是否存在恰为 \(m\)\(x\)。不难得出,\(m\) 可以冒泡到 \(a_m\) 上,当且仅当本轮冒泡开始前,\(m\) 的左边至少有两个数。而一个数 \(v<m\) 在本轮冒泡开始前出现在 \(m\) 的左边等价于 \(pos_v<mx_m\),于是从小到大枚举 \(m\),维护 \(pos\) 的前缀最小值和次小值即可。

时间复杂度为 \(\mathcal{O}(n)\)

代码
#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using i128 = __int128;
using ui = unsigned int;
using ull = unsigned long long;
using u128 = unsigned __int128;
using ld = long double;
using pii = pair<int, int>;
const int MAXN = 2e6 + 5;

template<typename T> T lowbit(T x) { return x & -x; }
template<typename T> void chkMin(T &x, T y) { x = y < x ? y : x; }
template<typename T> void chkMax(T &x, T y) { x = x < y ? y : x; }
constexpr int lg2(ll x) { return 63 ^ __builtin_clzll(x); }
constexpr ll bitCeil(ll x) { return x == 1 ? 1ll : 1ll << lg2(x - 1) + 1; }

int n, a[MAXN], pos[MAXN], suf[MAXN];
ll ans;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		pos[a[i]] = i;
	}
	suf[n + 1] = 0;
	for (int i = n; i; --i) suf[i] = max(suf[i + 1], pos[i]);
	ll sum = 0;
	for (int m = 2; m <= n; ++m) {
		ans += sum + 1;
		sum += pos[m - 1] > suf[m];
	}
	auto [mn, smn] = minmax({pos[1], pos[2]});
	for (int m = 3; m <= n; ++m) {
		if (smn < suf[m]) {
			++ans;
			break;
		}
		if (pos[m] < mn) {
			smn = n;
			mn = pos[m];
		} else {
			chkMin(smn, pos[m]);
		}
	}
	cout << ans;
	return 0;
}
posted @ 2026-07-03 20:16  P2441M  阅读(10)  评论(0)    收藏  举报