AcWing 788. 逆序对的数量

AcWing 788. 逆序对的数量

一、题目描述

给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i<j
a[i]>a[j],则其为一个逆序对;否则不是。

输入格式
第一行包含整数 n,表示数列的长度。

第二行包含 n 个整数,表示整个数列。

输出格式
输出一个整数,表示逆序对的个数。

数据范围
1n100000,数列中的元素的取值范围 [1,109]

输入样例:

6
2 3 4 5 6 1

输出样例:

5

二、什么是逆序对?

大的在前,小的在后,这样有一个算一个。

逆序对包括:{5,2},{5,2},{5,1},{5,4},{2,1},{2,1},共6个。

三、暴力大法

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
int n;
const int N = 100010;
int a[N];
LL ans;
//暴力大法
/*测试数据
 5
 5 2 2 1 4
 */
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++)cin >> a[i];
    //暴力大法
    for (int i = 1; i <= n; i++)
        for (int j = i + 1; j <= n; j++)
            if (a[i] > a[j]) ans++;
    cout << ans << endl;
    return 0;
}

四、答案的数据类型

最终的ans定义为什么类型?就是说给定范围内的数据,产生的逆序对最多是多少个呢?本题中n<=100000,就是小于105。那么什么情况下逆序对最多呢?就是完全倒序的情况下逆序对最多,那么在有n个数据的情况下,有多少个逆序对呢?
对于第1个而言,有n1个逆序对,第2个有n2个逆序对,....,第n个有0个逆序对。
ans=(n1)+(n2)+(n3)+...+1=n(n1)2
本题n最大是105,所以ans最大值是ans=100000(1000001)/21e10÷2=5109
int的极限是2147483647,比2e9大一点点,就是说int装不下,需要用long long:

十年OI一场空,不开long long 见祖宗

毫无悬念,tle妥妥的。

五、多种解法对比

归并排序 树状数组 线段树+静态数组 线段树+离散化+STL 线段树+离散化+手写二分
执行时长 75ms 待测 344ms 342ms 284ms
AcWing
洛谷

总结:STL的二分常数较大,尽量手写二分答题,减少TLE机会。

六、利用归并求逆序对

Q:为什么归并排序可以求逆序对呢?

答:在归并两个子数组时,有三种情况:

(1)、逆序对在左边(红色)

这在调用左侧子数组进行递归时,已经完成了统计,自己不管。(内部问题内部解决)

(2)、逆序对在右边(绿色)
这在调用右侧子数组进行递归时,已经完成了统计,自己不管。(内部问题内部解决)

(3)、逆序对在左侧和右侧(黄色)
这个需要自己来处理,对于右侧的黄色圆,那么在左侧的黄色圆及左侧黄色圆后面,一直到中线的所有数,都是比右侧黄色圆大的,就是有midi+1个逆序对。

可以结合代码来理解原理,其实就是在归并的时刻,只处理合并数组中大小反着的两个数字,一旦发现前面的数字比后面的数字大,就是发现了一个逆序对,同时,由于归并排序的特点,两个要归并的数组是内部有序的,所以,意味着左侧当前数字及左侧当前数字一直到左侧集合结束的所有数字,都比右侧当前数字大!个数就是midi+1个,其中l<=i<=mid

七、归并代码

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;

const int N = 100010;
int n;    // n个数据
int q[N]; // 原数组
int t[N]; // 临时数组
LL res;   // 结果

void merge_sort(int q[], int l, int r) {
    if (l >= r) return;
    int mid = (l + r) >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);

    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r)
        if (q[i] <= q[j])
            t[k++] = q[i++];
        else {
            t[k++] = q[j++];
            res += mid - i + 1;
        }
    while (i <= mid) t[k++] = q[i++];
    while (j <= r) t[k++] = q[j++];
    for (i = l, j = 0; i <= r; i++, j++) q[i] = t[j];
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> q[i];
    merge_sort(q, 1, n);
    printf("%d\n", res);
    return 0;
}

八、树状数组解法

因为问题比较复杂,另开一个博文水一篇题解

九、线段树+静态数组

因为问题比较复杂,另开一个博文水一篇题解

posted @   糖豆爸爸  阅读(1113)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2018-09-03 飞秋无法显示局域网好友
2017-09-03 东师理想运维工具开发路线图(第一阶段)
2017-09-03 手动安装gcc 4.8.5
Live2D
点击右上角即可分享
微信分享提示