1 条题解

  • 0
    @ 2026-5-16 16:01:52

    根据标程思路,下面给出本题的详细题解。


    题意回顾

    给定一个长度为 nn 的排列 p1,p2,,pnp_1, p_2, \dots, p_n
    对于每个位置 ii,可以选择 ai=pia_i = p_iai=2npia_i = 2n - p_i。目标是使数组 aa 的逆序对数最少,求该最小值。


    核心观察

    考虑排列中的最小值 11,设其所在位置为 ii

    • 若选择 ai=1a_i = 1,则 aia_i 是整个数组的最小值,它与左边所有元素(共 i1i-1 个)都构成逆序对,与右边元素不构成任何逆序对,贡献为 i1i-1
    • 若选择 ai=2n1a_i = 2n - 1,则 aia_i 变为最大值,它与右边所有元素(共 nin-i 个)构成逆序对,与左边无逆序对,贡献为 nin-i

    这两个选择带来的逆序对数量仅与 ii 的位置有关,与其他元素的具体选择完全无关。因此,为了最小化总逆序对数,我们可以直接贪心地选择贡献较小的方案,即取 min(i1,ni)\min(i-1,\, n-i)

    之后,将元素 11 从原排列中移除,剩余 n1n-1 个元素相对大小关系不变,相当于原排列中的 22 变成了新的最小值 11',问题规模缩小为 n1n-1,且与原问题完全同构。递归地应用上述策略即可得到全局最优解。


    算法步骤

    上述过程等价于:对排列中的每个元素 pip_i,分别计算它左侧比 pip_i 大的元素个数 lbiggeri\text{lbigger}_i 和右侧比 pip_i 大的元素个数 rbiggeri\text{rbigger}_i,然后将 min(lbiggeri, rbiggeri)\min(\text{lbigger}_i,\ \text{rbigger}_i) 累加到答案中。

    为什么这样可以?
    当按照元素值从小到大的顺序依次处理时,对于当前最小值 xx,它左侧比 xx 大的元素个数就是 lbiggerx\text{lbigger}_x,右侧比 xx 大的元素个数就是 rbiggerx\text{rbigger}_x。移除 xx 后,剩下的元素中,原来的 x+1x+1 成为新的最小值,它的 lbigger\text{lbigger}rbigger\text{rbigger} 在当前剩余集合中仍保持原来的数值(因为移除的 xx 比它小,不影响比它大的计数)。因此,对所有元素独立计算并取 min\min 再求和即得到答案。


    实现细节

    由于 n5000\sum n \le 5000,可以在 O(n2)O(n^2) 时间内直接计算。
    对于每个位置 ii

    • lbiggeri=\text{lbigger}_i = {j1j<i, pj>pi}\{j \mid 1 \le j < i,\ p_j > p_i\} 的数量。
    • rbiggeri=\text{rbigger}_i = {ji<jn, pj>pi}\{j \mid i < j \le n,\ p_j > p_i\} 的数量。

    然后累加 min(lbiggeri, rbiggeri)\min(\text{lbigger}_i,\ \text{rbigger}_i)

    也可以用树状数组优化到 O(nlogn)O(n \log n),但本题数据范围不需要。


    正确性证明(归纳)

    1. n=1n=1 时,逆序对数为 00,公式正确。
    2. 假设对长度为 n1n-1 的排列,按上述方法得到的值是最小逆序对数。
    3. 对于长度为 nn 的排列,取最小值 11 所在位置 ii,无论其他元素如何选择,11 变为 112n12n-1 造成的逆序对贡献是独立的,贪心选择 min(i1,ni)\min(i-1, n-i) 不会使总答案更劣。移除 11 后,剩余部分的最优解由归纳假设给出。

    因此算法正确。


    复杂度

    • 时间复杂度:每个测试用例 O(n2)O(n^2),总复杂度 O(n2)2.5×107O(\sum n^2) \le 2.5 \times 10^7,在时限内。
    • 空间复杂度:O(n)O(n)

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            vector<int> p(n);
            for (int i = 0; i < n; ++i) {
                cin >> p[i];
            }
            int ans = 0;
            for (int i = 0; i < n; ++i) {
                int lbigger = 0, rbigger = 0;
                for (int j = 0; j < i; ++j) {
                    if (p[j] > p[i]) lbigger++;
                }
                for (int j = i + 1; j < n; ++j) {
                    if (p[j] > p[i]) rbigger++;
                }
                ans += min(lbigger, rbigger);
            }
            cout << ans << " \n";
        }
        return 0;
    }
    
    • 1

    信息

    ID
    7119
    时间
    1000ms
    内存
    256MiB
    难度
    4
    标签
    递交数
    1
    已通过
    1
    上传者