2022-11-02 Acwing每日一题
逆序对的个数
给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。
输入格式
第一行包含整数 n,表示数列的长度。
第二行包含 n 个整数,表示整个数列。
输出格式
输出一个整数,表示逆序对的个数。
数据范围
1≤n≤100000,
数列中的元素的取值范围 [1,109]。
输入样例:
6
2 3 4 5 6 1
输出样例:
5
个人解析:
根据题意可知这是一道求逆序对的题,我想第一次碰到这种题都是暴力求解吧,但这里我们其实也可以使用归并排序和树状数组(不会😭),因此我也只能先用归并排序求解一遍了。
算法实现:
主要就是归并排序,只不过多了一行代码罢了。,其主要思想在于分治和单调性。
那么我们先来看归并排序的实现:
1.先将数组从中间一分为二,形成两个子数组,然后不断递归分割下去,直到剩下一个数时停止。不断对递归过程中产生的左右两个子数组进行合并排序(这里可以使用双指针优化),记住我们是先递归再优化,因此数组长度是从小到大。如当两个数组长度为2时,对其进行合并排序成一个长度为4的数组,如果数组长度不是偶数,则根据向下取整的特性选取中点。除此之外,左右两个子数组也都是单调的,毕竟是先排序再合并的。
2.在第1步使用双指针排序数组,即先创建一个temp
数组作为中间数组,将左数组的一个数与右数组的一个数进行比较,谁小谁先放进temp
(升序),直至某一个数组排序完成就退出排序过程,但如果另一个数组依然存在数的话,也一定是较大的,直接接到temp
数组后面即可。
3.用排序后的新数组temp
覆盖掉原来对应位置上的旧数组就相当于完成了对这一部分的合并排序,并将其用在后续的排序中。
需要格外注意的是,基于单调性我们可以知道当左数组存在一个数x比右数组的一个数y要大时,那么由于左数组是单调递增的,则从x往后的所有数都比y要大,因此逆序对的个数是从x开始到左子数组结束,也就是mid-i+1
(i是x在左子数组中的下标)
代码实现:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
void merge_sort(int q[],int l,int r,long long *ans){
if(l >= r) return ;
int tmp[100000];
int mid = (l+r) >> 1;
merge_sort(q,l,mid,ans);
merge_sort(q,mid+1,r,ans);
int i=l,j = mid+1,cnt = 0;
while(i <= mid && j <= r){
if(q[i] <= q[j]){
tmp[cnt ++] = q[i++];
}
else{
tmp[cnt ++] = q[j++];
*ans += mid - i + 1; // 分治+单调即可
}
}
while(i <= mid) tmp[cnt++] = q[i++];
while(j <= r) tmp[cnt++] = q[j++];
for(i=l,j=0; i<=r ; ++i, ++j){
q[i] = tmp[j];
}
}
int main()
{
long long ans = 0;
int a[100000] = {0};
int n;
cin >> n;
for(int i=0; i < n ; ++i) cin >> a[i];
merge_sort(a,0,n-1,&ans);
printf("%lld",ans);
return 0;
}