You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.6 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 788. 逆序对的数量

一、题目描述

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

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

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

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

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

数据范围 1≤n≤100000,数列中的元素的取值范围 [1,10^9]

输入样例:

6
2 3 4 5 6 1

输出样例:

5

二、解题思路

梳理概念

逆序对,就是对于任意两个序列中的数字,值大的,反而出现在前方,就是一个逆序对。

求解思路

① 由于是顺序出现了问题,才会产生逆序对,所以,结果肯定与顺序相关,也就是,需要进行排序。 ② 按什么进行排序呢?无外乎是按值,或者是按输入顺序。 那我们按顺序来吧,然后将树状数组的个数定义成数值的上限?一看1e9,直接傻了吧,开不了那么大的数组。

没办法,只能是按值来了。

将数组元素包装成结构体,然后按数值由大到小进行排序。(其实由小到大也是一样的,可以自行推导一下)

那要是两个数的数值一样呢?那谁在前谁在后呢?

注意:这道题自定义排序参数cmp的实现,不能单纯的val<t.val,如果相等的话也要保证位置不变,不然贡献会增多,这是因为如果数值一样并且序号小的先进入了树状数组等后面数值一样并且序号大的再进入时就会错误的把前面那个数值一样并且序号小的也认为是与自己形成逆序的1个数字其实人家和你一样不是逆序。而反之如果值一样序号大的先进入树状数组也可以有效避免这个问题。

③ 好了,现在排好顺序了,一个个进行吧。进入后,检查自己前面数字的个数,也就是比自己大(因为人家是先进来的),并且序号比自己小(因为是你左侧的,可不序号比你小),那就用统计一下有多少个吧,也就是sum求和啊,也就是树状数组的特长,动态前缀和啊~

Q:为什么要离散化呢?

其实使用树状数组进行计数时add(x,1),类似于桶计数,需要准备桶的数量是和数的大小直接相关的,本题中数值的小限是1e9,C++开这么大的数组肯定是死翘翘,所以,穷则思变。 虽然数值很大,但是个数并不多,n<=100000,这是可以开1e5这么大的数组的,所以,想到了离散化:将原来的数值由小到大排序,然后一一映射到1\sim 1e5这么大的空间上就行了。

Code

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;

const int N = 500010;

int n;
int a[N], q[N], ql;

// 二分模板 lower_bound (左闭右开)
int find(int x) {
    return lower_bound(q + 1, q + 1 + ql, x) - q;
}
// 树状数组模板
#define lowbit(x) (x & -x)
int c[N];
void add(int x, int v) {
    while (x < N) c[x] += v, x += lowbit(x);
}
LL sum(int x) {
    LL res = 0;
    while (x) res += c[x], x -= lowbit(x);
    return res;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]), q[i] = a[i]; // 复制出来一份q[i],用于排序+去重=离散化数组

    // 离散化 = 由小到大排序+去重
    sort(q + 1, q + n + 1);
    // 排序后,原数组原地去重,得到离散化后的数组
    ql = 1;
    for (int i = 2; i <= n; i++)
        if (q[i] != q[ql]) q[++ql] = q[i];

    LL res = 0;
    for (int i = 1; i <= n; i++) { // 捋着原数组来
        int x = find(a[i]);        // 通过二分算法找到a[i]映射的 离散化后q[]数组中找到对应的新下标
        // 由于数值排序是由小到大进行的,所以在我之前进入树状数组的,肯定是数值比我小的,同时,它们的位置还在我后面,就是逆序
        res += sum(ql) - sum(x); // sum(bl)-sum(x):从[x+1~bl]这段区间内的元素个数数量,也就是在枚举到x这个数时已经产生的比x这个数大的有多少个
        add(x, 1);               // 下标为x的位置个数数量+1
    }

    // 输出结果
    printf("%lld", res);
    return 0;
}