题解:P12865 [JOI Open 2025] 冒泡排序机 / Bubble Sort Machine

题意

给定长度为 \(n\) 的序列 \(a\)\(q\) 次操作,支持:

  1. \(a\) 进行一轮冒泡。
  2. 查询区间和。

\(2\leq n\leq 5\times 10^5\)\(1\leq q\leq 5\times 10^5\)\(1\leq a_i\leq 10^9\)

题解

我们首先指出:设经过 \(d\) 轮冒泡后得到的序列为 \(a'\),则 \(a'[1,x]\) 中的元素就是 \(a[1,\min(x+d,n)]\) 中的元素从小到大排序后的前 \(x\) 小。

证明:显然经过 \(d\) 轮冒泡后,\(a\) 中每个元素至多向前移动 \(d\) 个位置,所以,只有 \(a[1,\min(x+d,n)]\) 中的元素可能成为 \(a'[1,x]\) 中的元素。我们将 \(a\) 截断为 \(a[1,\min(x+d,n)]\),由冒泡的性质,我们知道,\(a[1,\min(x+d,n)]\) 中的最大值会在第 \(1\) 次冒泡移动到右数第 \(1\) 个位置,次大值会在第 \(2\) 次冒泡移动到右数第 \(2\) 个位置……因此 \(d\) 轮冒泡后,\(a[1,\min(x+d,n)]\) 中的前 \(d\) 大都移动到了末尾,那么 \(a'[1,x]\) 中自然就剩下前 \(x\) 小了。\(\Box\)

我们把区间和差分成前缀和。设当前询问之前共冒泡了 \(d\) 轮,询问 \([1,x]\) 的区间和时,转化为求 \(a[1,\min(x+d,n)]\) 的前 \(x\) 大之和。离线询问,按 \(x\) 排序,变成支持插入一个数,查询前 \(k\) 大之和。离散化一下,权值 BIT 上倍增即可维护。时间复杂度 \(\mathcal{O}(n\log{n})\)

实现时,为了避免边界的讨论,离散化时不必去重。具体的细节可以看代码。

显然这种做法常数比线段树上二分或者主席树小。

代码

#include <iostream>
#include <algorithm>

using namespace std;

#define lowbit(x) ((x) & -(x))
#define chk_min(x, v) (x) = min((x), (v))
#define chk_max(x, v) (x) = max((x), (v))
typedef long long ll;
typedef pair<int, int> pii;
const int N = 5e5 + 5, LGN = 19 + 5;

int n, q, d, a[N], p[N];
ll ans[N];
pii b[N];
int sz, szq;
struct Query {
	int id, x, k, tp;
	bool operator<(const Query &qr) const { return x < qr.x; }
} qr[N << 1];

struct BIT {
	int c1[N];
	ll c2[N];
	inline ll query(int x) {
		ll res = 0;
		for (; x; x -= lowbit(x)) res += c2[x];
		return res;
	}
	inline void add(int x, ll v) {
		ll v2 = v * b[x].first;
		for (; x <= n; x += lowbit(x)) c1[x] += v, c2[x] += v2;
	}
	inline ll sum(int k) {
		int x = 0, s = 0;
		ll res = 0;
		for (int i = LGN - 5; i != -1; --i)
			if (x + (1 << i) <= n && s + c1[x + (1 << i)] <= k)
				s += c1[x += 1 << i], res += c2[x];
		return res;
	}
} ft;

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i], b[i] = {a[i], i};
    sort(b + 1, b + n + 1);
    for (int i = 1; i <= n; ++i) p[b[i].second] = i;
    cin >> q;
    for (int i = 1; i <= q; ++i) {
    	int op, l, r; cin >> op;
    	if (op == 1) ++d;
    	else {
    		cin >> l >> r;
    		qr[++szq] = {++sz, min(r + d, n), r, 1};
    		qr[++szq] = {sz, min(l - 1 + d, n), l - 1, -1};
    	}
    }
    sort(qr + 1, qr + szq + 1);
    for (int i = 1, j = 1; i <= szq; ++i) {
    	while (j <= qr[i].x) ft.add(p[j++], 1);
    	ans[qr[i].id] += ft.sum(qr[i].k) * qr[i].tp;
    }
    for (int i = 1; i <= sz; ++i) cout << ans[i] << '\n';
    return 0;
}
posted @ 2025-06-21 17:12  P2441M  阅读(36)  评论(0)    收藏  举报