top1编程
← 返回题目
题解

逆序对

1 条题解

  • 0
    @ 2026-8-5 23:52:33

    P4669 逆序对(基础)

    解题思路

    第一步,理解什么是逆序对。 一对数 nums[i] 和 nums[j],如果 i < j(左边在前)但 nums[i] > nums[j](左边的反而更大),就是一对逆序对,也就是"前面的数比后面的数大"。比如序列 3 1 2 里,3>1、3>2,一共 2 对。注意要求是"大于"而不是"大于等于",所以相等的两个数不算逆序对。

    第二步,想一个聪明的统计办法。 直接两两比较要 O(n²),n 很大时会超时。我们借用归并排序:归并排序要把两个已经各自有序的区间合并成一个,合并的时候正好可以顺手数出逆序对。

    第三步,合并时统计。 合并左区间 l~mid 和右区间 mid+1~r 时,两个区间内部都是升序的。用 i 指向左边的当前数、j 指向右边的当前数:如果 nums[i] <= nums[j],说明 nums[i] 不大于右边的数,不是逆序对,把它放进结果数组;如果 nums[i] > nums[j],说明右区间的 nums[j] 比左区间从 i 到 mid 的所有数都小,这些数全都和 nums[j] 构成逆序对,一共 mid-i+1 对,全部加进答案 ans。

    第四步,收尾。 把两边剩下的数依次放进结果数组,再把结果复制回原数组。每个逆序对恰好在某一次合并中被统计一次,不会漏也不会重。

    想一想生活里的例子。 老师按学号点名,如果发现"后面的同学比前面的同学高",就算一对"高低颠倒"。

    边界情况: n 最多 2500,其实两重循环也能过,但归并排序 O(n log n) 更通用;数字最大 1e9 用 int 存得下,但逆序对最多约 n(n-1)/2 对,答案要用 long long 存。

    参考代码

    // P4669 逆序对:归并排序过程中统计 nums[i] > nums[j] 且 i < j 的对数
    #include <iostream>
    int nums[1000005], temp[1000005];
    long long ans = 0;
    
    void mergeSort(int l, int r) {
        if (l >= r) return;
        int mid = (l + r) / 2;
        mergeSort(l, mid);
        mergeSort(mid + 1, r);
        int i = l, j = mid + 1, k = l;
        while (i <= mid && j <= r) {
            if (nums[i] <= nums[j]) temp[k++] = nums[i++];
            else {
                ans += (long long)(mid - i + 1);   // 左边剩余的数都比 nums[j] 大,全是逆序对
                temp[k++] = nums[j++];
            }
        }
        while (i <= mid) temp[k++] = nums[i++];
        while (j <= r) temp[k++] = nums[j++];
        for (int p = l; p <= r; p++) nums[p] = temp[p];
    }
    
    int main() {
        int n;
        std::cin >> n;
        for (int i = 0; i < n; i++) std::cin >> nums[i];
        mergeSort(0, n - 1);
        std::cout << ans << "\n";
        return 0;
    }
    

    复杂度分析

    归并排序本身是 O(n log n),合并过程中顺手统计逆序对,不增加额外的时间复杂度。n 最大 2500,即使 n 更大一些也能轻松处理。空间上需要两个数组 nums 和 temp,O(n)。答案可能很大,用 long long 保存,避免 int 溢出。这是"排序算法边排序边做事"的经典应用。

    • 1