前言
题目链接:洛谷。
其他题解十分生硬地给出了 t[]
的定义,本题解按照解题思路,层层递进,自然得出题目解法。
题意简述
以下是冒泡排序的一轮:
void bubble_sort(int val[], int l, int r) {for (int i = l; i < r; ++i)if (val[i] > val[i + 1])swap(val[i], val[i + 1]);
}
定义 \((p, p+1)\) 是 \(\{a_n\}\) 的一个「分割点」当 \(\max\limits_{i=1}^{p}a_i\leq\min\limits_{i=p+1}^na_i\)。
以下是一个基于分治和冒泡排序的排序算法:
void qsort(int val[], int l, int r){if (l == r) return;do {bubble_sort(val, l, r);work_cnt += r - l + 1;} while (!check(val, l, r));// check(val, l, r) 返回 val[l~r] 中是否存在「分割点」divide_and_qsort_each_piece(val, l, r);// 将 val[l~r] 按照「分割点」分割成若干子问题,并递归调用 qsort
}
给你长度为 \(n\) 的序列 \(\{a_n\}\),求运行 qsort(a, 1, n)
后,全局变量 work_cnt
的值。
\(n \leq 10^5\)。
题目分析
显然我们需要分析冒泡排序的性质。
一轮冒泡排序后,肯定会产生一个「分割点」。这是由于一轮冒泡排序后,序列中的最大值被冒泡到了最右侧,所以产生了一个「分割点」。所以那个 do...while
实际没有用处。
然后发现整个序列在排序过程中比较鬼畜,难以发现性质。于是考虑转变计数视角,求每一个元素被 bubble_sort
了多少次,答案就是每个元素答案之和。
对于一个元素,只要它没有到达最终位置,它会一直被 bubble_sort
,这在时间轴上体现为一段前缀,我们只要求出在什么时候这个元素不会被 bubble_sort
即可。发现,每个元素唯一的递归出口 l == r
的实际含义为,该元素左右两侧都出现了「分割点」,于是问题似乎可以被进一步规约到每一个「分割点」出现的时刻。
一个「分割点」出现,当该在它左侧的元素都在它左侧,该在它右侧的元素都在它右侧了。这么想是因为我们冒泡排序的经典思考方式,关注于每一个元素的移动。我们有一个经典结论:
经典结论:
对于一个元素,倘若它想要向左移动,它在一轮冒泡中会恰向左移动一个位置。
考虑在一轮冒泡中,它会且只会和其左侧的最大值 swap
,然后向左移动一个位置。
所以我们考虑在某一个「分割点」右侧的元素,什么时候到这个「分割点」左侧。所需的冒泡轮数,根据我们的结论,就是它到「分割点」的距离。那么对于所有在它右侧且想要跑到它左侧的元素,求出位置最靠右的元素的位置,即可求出这个「分割点」出现的时间。
接下来随便求了。给出一种可能的实现方式:设 \(b_i\) 表示排序后的序列 \(a'\) 的第 \(i\) 个元素 \(a'_i\) 为原序列 \(a_{b_i}\),再设 \(\operatorname{mxR}_i\) 表示 \(1\sim i\) 中,目标位置最右在哪里。那么 \(t_i=\operatorname{mxR}_i-i\) 就是「分割点」\((i, i+1)\) 出现的时刻。倘若其值小于 \(1\),说明该「分割点」一开始便存在,对 \(1\) 取 \(\max\) 即可。答案根据我们的分析为 \(\sum\limits_{i=1}^n\max\{t_{i-1},t_i\}\),其中 \(t_0=1\)。\(b_i\) 排序以下即可,很好求,不难发现 \(\operatorname{mxR}_i\) 即为 \(b_i\) 的前缀最大值。
时间复杂度 \(\mathcal{O}(n\log n)\),瓶颈在于一开始的排序。
代码
实际实现起来很短。
#include <cstdio>
#include <algorithm>
using namespace std;const int N = 100010;int n, a[N], b[N], t[N];
long long ans;signed main() {scanf("%d", &n);for (int i = 1; i <= n; ++i) {scanf("%d", &a[i]);b[i] = i;}sort(b + 1, b + n + 1, [] (int x, int y) -> bool {if (a[x] == a[y]) return x < y; // notice this detailreturn a[x] < a[y];});t[0] = 1;for (int i = 1; i <= n; ++i) {b[i] = max(b[i], b[i - 1]); // mxR[i]t[i] = b[i] - i;if (t[i] <= 0) t[i] = 1;}for (int i = 1; i <= n; ++i)ans += max(t[i], t[i - 1]);printf("%lld", ans);return 0;
}