[USACO18OPEN] Out of Sorts P 题解

前言

题目链接:洛谷

其他题解十分生硬地给出了 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 detail
        return 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;
}
posted @ 2025-02-08 20:17  XuYueming  阅读(58)  评论(0)    收藏  举报