LOJ #3538. 「JOI Open 2018」冒泡排序 2 题解

Description

冒泡排序是一个对序列排序的算法。现在我们要将一个长度为 NN 的序列 A0,A1,,AN1A_0,A_1,\ldots ,A_{N-1}​ 按不降顺序排序。当两个相邻的数没有按正确顺序排列时,冒泡排序会交换这两个数的位置。每次扫描这个序列就进行这种交换。更确切地说,在一趟扫描中,对于 i=0,1,,N2i=0,1,\ldots ,N-2,并按这个顺序,如果 Ai>Ai+1A_i>A_{i+1}​,那么我们就交换这两个数的位置。众所周知任何序列经过有限趟扫描后一定可以按非降顺序排好序。对于一个序列 AA,我们定义用冒泡排序的扫描趟数为使用如上算法使得 AA 排好序的情况下所扫描的趟数。

JOI 君有一个长度为 NN 的序列 AA。他打算处理 QQ 次修改 AA 的值的询问。更明确地说,在第 (j+1) (0jQ1)(j+1)\ (0\le j\le Q-1) 次询问,AXjA_{X_j} 的值会变为 VjV_j

JOI 君想知道处理每次修改之后,用冒泡排序的扫描趟数。

N,Q5×105N,Q\leq 5\times 10^5

Solution

首先容易发现答案为 maxi=1n(j=1i1[aj>ai])\displaystyle\max_{i=1}^{n}{\left(\sum_{j=1}^{i-1}{[a_j>a_i]}\right)},直接维护需要用树套树或者分块,可能会比较慢。

注意到一个可能作为答案的 ii 一定是严格后缀最小值,否则换成任意一个后缀里比它更小的数会更优。当 ii 是后缀最小值时,上面那个式子可以弱化条件,即 j=1i1[aj>ai]=j=1n[aj>ai](ni)\sum_{j=1}^{i-1}{[a_j>a_i]}=\sum_{j=1}^{\color{red}{n}}{[a_j>a_i]}-(n-i)

因为减掉的 nin-i 只会更多,所以不会算优,而最优解一定可以用这个表示,那么对于每个数值维护其最后一次出现的位置即可。

时间复杂度:O((n+q)logn)O((n+q)\log n)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e6 + 5;

int n, q, m;
int a[kMaxN], x[kMaxN], v[kMaxN], unq[kMaxN];
std::set<int> st[kMaxN];

int getid(int x) { return std::lower_bound(unq + 1, unq + 1 + m, x) - unq; }

void discrete() {
std::sort(unq + 1, unq + 1 + m);
m = std::unique(unq + 1, unq + 1 + m) - (unq + 1);
for (int i = 1; i <= n; ++i) a[i] = getid(a[i]);
for (int i = 1; i <= q; ++i) v[i] = getid(v[i]);
}

struct SGT {
int mx[kMaxN * 4], tag[kMaxN * 4];
void pushup(int x) { mx[x] = std::max(mx[x << 1], mx[x << 1 | 1]); }
void addtag(int x, int v) { mx[x] += v, tag[x] += v; }
void pushdown(int x) {
if (tag[x]) addtag(x << 1, tag[x]), addtag(x << 1 | 1, tag[x]), tag[x] = 0;
}
void build(int x, int l, int r) {
if (l == r) return void(mx[x] = *st[l].rbegin() - n);
int mid = (l + r) >> 1;
build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
pushup(x);
}
void update(int x, int l, int r, int ql, int qr, int v) {
if (l > qr || r < ql) return;
else if (l >= ql && r <= qr) return addtag(x, v);
pushdown(x);
int mid = (l + r) >> 1;
update(x << 1, l, mid, ql, qr, v), update(x << 1 | 1, mid + 1, r, ql, qr, v);
pushup(x);
}
} sgt;

void update(int x, int v) {
sgt.update(1, 1, m, 1, a[x] - 1, -1);
int lst = *st[a[x]].rbegin();
st[a[x]].erase(x);
sgt.update(1, 1, m, a[x], a[x], *st[a[x]].rbegin() - lst);

a[x] = v;
sgt.update(1, 1, m, 1, a[x] - 1, 1);
lst = *st[a[x]].rbegin();
st[a[x]].emplace(x);
sgt.update(1, 1, m, a[x], a[x], *st[a[x]].rbegin() - lst);
}

void dickdreamer() {
std::cin >> n >> q;
for (int i = 1; i <= n; ++i) std::cin >> a[i], unq[++m] = a[i];
for (int i = 1; i <= q; ++i) {
std::cin >> x[i] >> v[i];
++x[i];
unq[++m] = v[i];
}
discrete();
for (int i = 1; i <= m; ++i) st[i].emplace(-1e9);
for (int i = 1; i <= n; ++i) st[a[i]].emplace(i);
sgt.build(1, 1, m);
for (int i = 1; i <= n; ++i) sgt.update(1, 1, m, 1, a[i] - 1, 1);
for (int i = 1; i <= q; ++i) {
update(x[i], v[i]);
std::cout << sgt.mx[1] << '\n';
}
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

LOJ #3538. 「JOI Open 2018」冒泡排序 2 题解
https://sobaliuziao.github.io/2025/09/07/post/9284a97b.html
作者
Egg_laying_master
发布于
2025年9月7日
许可协议