【题解】逆序对

226 阅读4分钟

逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 ai>aja_i>a_ji<ji<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

输入格式

第一行,一个数 nn,表示序列中有 nn个数。

第二行 nn 个数,表示给定的序列。序列中每个数字不超过 10910^9

输出格式

输出序列中逆序对的数目。

样例 #1

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

提示

对于 25%25\% 的数据,n2500n \leq 2500

对于 50%50\% 的数据,n4×104n \leq 4 \times 10^4

对于所有数据,n5×105n \leq 5 \times 10^5

请使用较快的输入输出

题目分析

目的:求出序列中逆序对的数目。

首先确定逆序对的特征:

  1. 位置: i<ji<j
  2. 大小: ai>aja_i>a_j

问题可抽象为求满足条件的二元组数量。能较快地想到暴力的解法:双重循环枚举所有的二元组,筛选所有符合条件的元素即可,复杂度为 O(n2)O(n^2)

for(int i=1;i<=n;i++){
    for(int j=i+1;j<=n;j++){
        if(a[i]>a[j]) cnt++;
    }
}

根据数据范围可以获得 50%50\% 的分数。对于剩下的分数需要考虑优化方法。

当前速度慢在,需要一组组的找过去,每次也只能确定一组逆序对,若能一次性确定多组逆序对,自然就能加快统计的速度的。如何才能一次性就确定多组逆序对了呢?

假设序列存在升序的一段 [L,R][L,R]ii 在升序区间内, jj 在升序区间右侧。若 ai>aja_i>a_jaia_iaja_j 可以构成一组逆序对,且 aiaRa_i \sim a_R 的元素也能与 aja_j 构成逆序对,因为从位置上来看,它们位于 jj 的左侧,数值上他们又是大于 aja_j 的所以符合逆序对特征。

在这样的比较过程中,我们需要序列呈现有序的状态,比较的位置的相对位置需要与原序列一致,我们可以利用归并排序来实现这一效果。

先看归并排序的模板:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N],b[N];
void merge(int l1,int r1,int l2,int r2){
	int i=l1,j=l2,k=l1;
	while(i<=r1 && j<=r2){
		if(a[i]<a[j]){
			b[k++]=a[i++];
		}else{
			b[k++]=a[j++];
		}
	}
	while(i<=r1) b[k++]=a[i++];
	while(j<=r2) b[k++]=a[j++];
	for(int i=l1;i<=r2;i++){
		a[i]=b[i];
	}
}
void mergeSort(int l,int r){
	if(l>=r) return ;
	int mid=(l+r)/2;
	//[l,mid] [mid+1,r]
	//先使得左半部分有序、右半部分有序
	mergeSort(l,mid);
	mergeSort(mid+1,r);
	//合并两个有序的数列
	merge(l,mid,mid+1,r);
}

观察merge合并过程中的 iijj 。首先从位置关系上可判断出 i<ji<j,若 ai>aja_i>a_j 我们便能推出 aia_iaja_j 构成逆序对,并且由于合并的两段序列都是升序的,所以 aiar1a_i \sim a_{r_1} 都能与 aja_j 构成逆序对,因为位置上 ir1<ji\le r_1 <j ;数值上 aj<aiar1a_j<a_i\le a_{r_1} 。那么一下子就能确定 r1i+1r1-i+1 组逆序对。

逆序对.png

此时我们只需要在归并排序的过程中进行处理,就能统计出所有的逆序对数量了,复杂度为 O(nlogn)O(n\log n)

代码实现

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
typedef long long ll;
int a[N],b[N];
ll sum;
void merge(int l1,int r1,int l2,int r2){
   int i=l1,j=l2,k=l1;
   while(i<=r1 && j<=r2){
   	if(a[i]>a[j]){
   		sum+=(r1-i+1);//a[i] ~ a[r1] 都与 a[j]形成逆序对
   		b[k++]=a[j++];
   	}else{
   		b[k++]=a[i++];
   	}
   }
   while(i<=r1) b[k++]=a[i++];
   while(j<=r2) b[k++]=a[j++];
   for(int i=l1;i<=r2;i++){
   	a[i]=b[i];
   }
}
void mergeSort(int l,int r){
   if(l>=r) return ;
   int mid=(l+r)/2;
   //[l,mid] [mid+1,r]
   //先使得左半部分有序、右半部分有序
   mergeSort(l,mid);
   mergeSort(mid+1,r);
   //合并两个有序的数列
   merge(l,mid,mid+1,r);
}
int main()
{
   int n;
   cin>>n;
   for(int i=1;i<=n;i++){
   	cin>>a[i];
   }
   mergeSort(1,n);
   cout<<sum;
   return 0;
}