目录
题目介绍
给定一个长度为 nn 的整数数列,请你计算数列中的逆序对的数量。逆序对的定义如下:对于数列的第 ii 个和第 jj 个元素,如果满足 i<ji<j 且 a[i]>a[j]a[i]>a[j],则其为一个逆序对;否则不是。
输入格式
第一行包含整数 nn,表示数列的长度。
第二行包含 nn 个整数,表示整个数列。
输出格式
输出一个整数,表示逆序对的个数。
数据范围
1≤n≤1000001≤n≤100000,
数列中的元素的取值范围 [1,109][1,109]。
输入样例:
6
2 3 4 5 6 1
输出样例:
5
算法介绍
这道题目非常有含金量,能帮助完全理解归并排序的实质。我们先说一下归并排序,我们把一个数组分为左右两个部分
然后单独看这两个部分
运用x,y两个指针,不断比较两个部分中数的大小,并将较小数不断存入一个新建数组中,即若x<y,则将x存入数组temp,x向后偏移,再次与y比较,直到x,y中有一个指向末端,我们再将未存入temp的数直接存入temp(可以直接存入,因为已经是有序的了)。
问题分析
求逆序对即是找所有前面比后面大的两个数,如果通过遍历来找几乎不可能,我们只有利用归并排序的性质,当x和y比较后会传较小的那个数,假设x是左,y是右,那么每次如果是y指向的数传入temp中则说明此数与x所指向的数形成逆序对,这时我们就可以记录一次。
然而经检测,这样记录并不全面,当划分数的个数为奇数时,最后补入temp中的数若形成逆对数则没有被记录。
列如:
此时5和6都与1形成逆对数,但是却只记录了一次。这时我们就要找隐藏的逻辑了,即所记录的逆序对数量其实与数的个数相关,我们可以简化为与数组下标(指针)相关,当形成逆序对时,我们不但要+1,还要加上下标差,5和6是相邻的,下标刚好差1。这时候又会有一个隐患,如果不是5、6和1而是5、3和4怎么办呢?按理来说3和4不形成逆序对,但是我们却加了他们的下标差,也就是多加了1,其实算法本身已经规避了这种情况。
算法中,在比较x和y时我们先就会进行递归不断划分数组,直到数组无法被分为两部分,所以上述例子中5和6也会被划分,只是不形成逆对数,不做记录。
代码展示
#include<iostream>
using namespace std;
const int N = (int)1e5 + 10;
int q[N], temp[N];
int n;
long int a = 0;
void mergesort(int q[],int l,int r)
{
if (l >= r) return;
int mid = l + r >> 1;
mergesort(q, l, mid), mergesort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
{
if (q[i] <= q[j])
{
temp[k++] = q[i++];
}
else
{
temp[k++] = q[j++];
a = a + mid - i + 1;
}
}
while (i <= mid) temp[k++] = q[i++];
while (j <= r) temp[k++] = q[j++];
for (i = l, j = 0; i <= r; i++, j++) q[i] = temp[j];
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &q[i]);
mergesort(q, 0, n-1);
cout << a << endl;
return 0;
}
总结
归并排序要点是它将数组不断拆分为两部分,利用递归返回排好序的数组。