逆序对
题目描述
猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 $a_i>a_j$ 且 $i<j$ 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。
Update:数据已加强。
输入格式
第一行,一个数 $n$,表示序列中有 $n$个数。
第二行 $n$ 个数,表示给定的序列。序列中每个数字不超过 $10^9$。
输出格式
输出序列中逆序对的数目。
样例 #1
样例输入 #1
6
5 4 2 6 3 1
样例输出 #1
11
提示
对于 $25\%$ 的数据,$n \leq 2500$
对于 $50\%$ 的数据,$n \leq 4 \times 10^4$。
对于所有数据,$n \leq 5 \times 10^5$
请使用较快的输入输出
应该不会 $O(n^2)$ 过 50 万吧 by chen_zhe
题目分析
目的:求出序列中逆序对的数目。
首先确定逆序对的特征:
- 位置: $i<j$
- 大小: $a_i>a_j$
问题可抽象为求满足条件的二元组数量。能较快地想到暴力的解法:双重循环枚举所有的二元组,筛选所有符合条件的元素即可,复杂度为 $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\%$ 的分数。对于剩下的分数需要考虑优化方法。
当前速度慢在,需要一组组的找过去,每次也只能确定一组逆序对,若能一次性确定多组逆序对,自然就能加快统计的速度的。如何才能一次性就确定多组逆序对了呢?
假设序列存在升序的一段 $[L,R]$ , $i$ 在升序区间内, $j$ 在升序区间右侧。若 $a_i>a_j$ 则 $a_i$ 与 $a_j$ 可以构成一组逆序对,且 $a_i \sim a_R$ 的元素也能与 $a_j$ 构成逆序对,因为从位置上来看,它们位于 $j$ 的左侧,数值上他们又是大于 $a_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
合并过程中的 $i$ 和 $j$ 。首先从位置关系上可判断出 $i<j$,若 $a_i>a_j$ 我们便能推出 $a_i$ 与 $a_j$ 构成逆序对,并且由于合并的两段序列都是升序的,所以 $a_i \sim a_{r_1}$ 都能与 $a_j$ 构成逆序对,因为位置上 $i\le r_1 <j$ ;数值上 $a_j<a_i\le a_{r_1}$ 。那么一下子就能确定 $r1-i+1$ 组逆序对。
此时我们只需要在归并排序的过程中进行处理,就能统计出所有的逆序对数量了,复杂度为 $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;
}