【题目来源】
https://www.luogu.com.cn/problem/P1908
【题目描述】
猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 ai>aj 且 i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。
【输入格式】
第一行,一个数 n,表示序列中有 n 个数。
第二行 n 个数,表示给定的序列。序列中每个数字不超过 10^9。
【输出格式】
输出序列中逆序对的数目。
【输入样例】
6
5 4 2 6 3 1
【输出样例】
11
【说明/提示】
对于 25% 的数据,n≤2500。
对于 50% 的数据,n≤4×10^4。
对于所有数据,n≤5×10^5。
请使用较快的输入输出。
【算法分析】
● 归并排序 → 逆序对
归并排序是一种有效的排序算法,它不仅可以对数组进行排序,还可以在排序的过程中顺便计算逆序对的数量。逆序对是指数组中满足 i < j 但 a[i] > a[j] 的元素对 ( a[i], a[j] )。求逆序对数量的代码,只需微调归并排序代码即可。
本题的求逆序对代码,脱胎于如下归并排序代码。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn],t[maxn];
int n;
void merge_sort(int le,int ri) {
if(le==ri) return;
int mid=(le+ri)>>1;
merge_sort(le,mid);
merge_sort(mid+1,ri);
int p=le;
int i=le,j=mid+1;
while(i<=mid && j<=ri) { //double pointer
if(a[i]<a[j]) t[p++]=a[i++];
else t[p++]=a[j++];
}
while(i<=mid) t[p++]=a[i++];
while(j<=ri) t[p++]=a[j++];
for(int i=le; i<=ri; i++) a[i]=t[i];
}
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
}
merge_sort(1,n); //subscript
for(int i=1; i<=n; i++) {
if(i!=n) printf("%d ",a[i]);
else printf("%d\n",a[i]);
}
return 0;
}
/*
in:
5
4 2 4 5 1
out:
1 2 4 4 5
*/
● 本题需注意序列中可能有重复数字。故代码第 19 行为 a[i]<=a[j],而不是 a[i]<a[j]。代码中第 22 行 cnt+=(mid-i+1),表示当 a[i]>a[j] 时,a[i],…,a[mid] 都与 a[j] 构成逆序对。
● 在 C++ 中,int 类型通常能表示的数值范围是从 -2,147,483,648 ~ 2,147,483,647,也即 -2^31 ~ 2^31-1,小于 2.15×10^9。简单起见,若整数大于 2×10^9,就选择使用 long long 型。
详见:https://blog.csdn.net/hnjzsyjyj/article/details/132240042
● 在 C++ 中,若输入数据个数大于 10^5 时,推荐使用 scanf 而不是 cin 输入数据。这是因为 scanf 通常比 cin 更快。
详见:https://blog.csdn.net/hnjzsyjyj/article/details/145618674
【算法代码】
下面是求逆序对数量的代码,脱胎归并排序代码。在其中,分别用 add code 1、add code 2、add code 3 标示了相对于归并排序代码添加的代码。
#include <bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
int a[maxn],t[maxn];
long long cnt; //add code 1
int n;
void merge_sort(int le,int ri) {
if(le==ri) return;
int mid=(le+ri)>>1;
merge_sort(le,mid);
merge_sort(mid+1,ri);
int p=le;
int i=le,j=mid+1;
while(i<=mid && j<=ri) { //double pointer
if(a[i]<=a[j]) t[p++]=a[i++]; //a[i]<=a[j]
else {
t[p++]=a[j++];
cnt+=(mid-i+1); //add code 2
}
}
while(i<=mid) t[p++]=a[i++];
while(j<=ri) t[p++]=a[j++];
for(int i=le; i<=ri; i++) a[i]=t[i];
}
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
}
merge_sort(1,n); //subscript
printf("%lld\n",cnt); //add code 3
// If want to output the sorted array,
// remove the following comments.
/*for(int i=1; i<=n; i++) {
if(i!=n) printf("%d ",a[i]);
else printf("%d\n",a[i]);
}*/
return 0;
}
/*
in:
6
5 4 2 6 3 1
out:
11
*/
【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/145679752
https://blog.csdn.net/hnjzsyjyj/article/details/132685017