1 条题解

  • 0
    @ 2026-5-14 13:06:54

    题意简述

    给定一个 11nn 的排列 p1,p2,,pnp_1,p_2,\dots,p_n
    构造数组 aia_i,每个 aia_i 要么等于 pip_i,要么等于 2npi2n - p_i
    求构造出的 aa最少的逆序对数量

    逆序对:i<ji<jai>aja_i > a_j


    关键观察

    对于原排列中的数 pip_i,它的两种可能取值是:

    • xi=pix_i = p_i
    • yi=2npiy_i = 2n - p_i

    注意 pip_i 取值范围 1n1 \dots n,因此 2npi2n-p_i 取值范围 n2n1n \dots 2n-1

    也就是说,第一种取值在 [1,n][1, n],第二种取值在 [n,2n1][n, 2n-1]

    重要性质
    所有 xix_i 互不相同(因为是排列),所有 yiy_i 也互不相同,并且 xi<yix_i < y_i,且 xi+yi=2nx_i + y_i = 2n
    另外,yi=2nxiy_i = 2n - x_i,所以若 xi<xjx_i < x_j,则 yi>yjy_i > y_j


    贪心策略

    我们想让逆序对尽可能少,即希望数组 aa 尽量非降序

    从左到右处理,每次选择 xix_iyiy_i,尽可能让当前值 \ge 上一个选的值。

    设上一个选的值为 last\text{last}

    当前两个候选:

    • xi=pix_i = p_i
    • yi=2npiy_i = 2n - p_i

    情况分类

    1. 两个候选都 last\ge \text{last}
      选较小的那个(xix_i),有利于后面。

    2. 只有一个候选 last\ge \text{last}
      选那个可行的。

    3. 两个候选都 <last< \text{last}
      无论选哪个都会产生逆序对(相对上一个位置)。
      选较小的那个,减小对后续的影响。


    为什么贪心正确?

    因为 xix_iyiy_i 的和固定为 2n2n,且大小分布对称。
    如果上一个值已经很大(比如接近 2n2n),那么选 yiy_i 可能更差;如果上一个值较小,选 xix_i 更优。
    这个贪心实际上是在维护一个尽可能小的“当前最大值”,从而最小化未来逆序对数。
    可以证明,不存在一个局部选择会让全局更优,因为逆序对只与相邻的相对顺序有关,局部最优不会破坏全局最优(类似最小化上升序列的逆序对,是单调决策问题)。


    算法步骤

    1. 初始化 last=1\text{last} = -1(表示还没选)。
    2. i=1i = 1nn
      • x=pix = p_i
      • y=2npiy = 2n - p_i
      • 如果 last=1\text{last} = -1:选 min(x,y)\min(x, y)
      • 否则:
        • 如果 xlastx \ge \text{last}ylasty \ge \text{last}:选 min(x,y)\min(x, y)
        • 否则如果 xlastx \ge \text{last}:选 xx
        • 否则如果 ylasty \ge \text{last}:选 yy
        • 否则:选 min(x,y)\min(x, y)
      • 更新 last=选的值\text{last} = \text{选的值}
    3. 最后统计构造出的 aa 数组的逆序对(O(n2)O(n^2)O(nlogn)O(n \log n) 树状数组,因为 n5000n \le 5000O(n2)O(n^2) 可行)。

    复杂度

    • 时间:O(n2)O(n^2) 每个测试(逆序对统计),总 nn5000\le 5000,可通过。
    • 空间:O(n)O(n)

    标程(C++)

    #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];
            
            vector<int> a(n);
            int last = -1;
            for (int i = 0; i < n; i++) {
                int x = p[i];
                int y = 2 * n - p[i];
                if (last == -1) {
                    a[i] = min(x, y);
                } else {
                    if (x >= last && y >= last) {
                        a[i] = min(x, y);
                    } else if (x >= last) {
                        a[i] = x;
                    } else if (y >= last) {
                        a[i] = y;
                    } else {
                        a[i] = min(x, y);
                    }
                }
                last = a[i];
            }
            
            int inv = 0;
            for (int i = 0; i < n; i++) {
                for (int j = i + 1; j < n; j++) {
                    if (a[i] > a[j]) inv++;
                }
            }
            cout << inv << "\n";
        }
        return 0;
    }
    

    样例验证

    以题目第一个样例:

    2
    2 1
    
    • i=1i=1x=2,y=222=2x=2, y=2*2-2=2,选 22,last=2
    • i=2i=2x=1,y=3x=1, y=3x<lastx < lastylasty \ge last,选 y=3y=3 a=[2,3]a=[2,3],逆序对 00

    第二个样例:

    3
    2 1 3
    
    • i=1i=1x=2,y=4x=2, y=4,last=-1,选 22
    • i=2i=2x=1,y=5x=1, y=5x<lastx < last(1<2),ylasty \ge last(5≥2),选 55
    • i=3i=3x=3,y=3x=3, y=335?3 \ge 5? 否,35?3 \ge 5? 否,选 min(3,3)=3\min(3,3)=3(实际只有3) a=[2,5,3]a=[2,5,3]
      逆序对:(5,3) → 1 ✅

    这样贪心 + 直接统计逆序对就能得到正确的最小逆序对数。
    如果 nn 更大(比如 2×1052\times10^5),则需要用树状数组优化逆序对统计。

    • 1

    信息

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