题目描述
猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中ai>aj且i<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。
Update:数据已加强。
输入输出格式
输入格式:
第一行,一个数n,表示序列中有n个数。
第二行n个数,表示给定的序列。序列中每个数字不超过10^9109
输出格式:
给定序列中逆序对的数目。
输入输出样例
输入样例#1: 复制
6 5 4 2 6 3 1
输出样例#1: 复制
11
说明
对于25%的数据,n \leq 2500n≤2500
对于50%的数据,n \leq 4 \times 10^4n≤4×104。
对于所有数据,n \leq 5 \times 10^5n≤5×105
请使用较快的输入输出
应该不会n方过50万吧 by chen_zhe
逆序对的定义
设有一个序列,对于序列中任意两个元素ai,aj,若i<j,ai>aj,则说明ai和aj是一对逆序对。
逆序对的几种求法
1.冒泡排序:O()
写两层循环,时间复杂度为
2.归并排序:O(nlogn)
众所周知,归并排序就是将一个序列分成两部分进行排序,然后将排序后的两个序列中的元素一个一个插入(如图)
然后,我们发现,对于两个个正在排列的子序列,若后面的子序列中要插入元素,比如上图中要插入3,则前面的子序列中剩下的待排序元素都可以与当前插入的元素组成逆序对(因为前面待排序的元素都比这个元素大,而且下标都比这个元素的下标要小),而且再次递归下去的子序列也是如此。所以程序如下:
不过java会爆内存,改成c++可以过洛谷的10个样例,因为样例被加强了,虽然使用树状数组
import java.util.Scanner;
public class P1908_逆序对 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
a = new int[n];
for(int i=0;i<n;i++)
a[i] = in.nextInt();
System.out.println(mergeSort(0, n-1));
// for(int i=0;i<n;i++)
// System.out.print(a[i]+" ");
}
static int[] a;
static int n;
static int mergeSort(int l,int r) {
if(l>=r)
return 0;
int ans = 0;
int mid = l + (r-l)/2;
ans+=mergeSort(l, mid);
ans+=mergeSort(mid+1,r);
ans+=merge(l,r);
return ans;
}
static int merge(int l,int r) {
int mid = l + (r-l)/2;
int[] A = a.clone();
int ans = 0;
int i=l,j=mid+1;
int k=l;
while(i<=mid && j<=r) {
if(A[i]>A[j]) {
a[k++] = A[j++];
ans+=mid+1-i;
}else
a[k++] = A[i++];
}
while(i<=mid)//2 4 5,1 3 6
a[k++] = A[i++];
while(j<=r){
a[k++] = A[j++];
}
// System.out.println(l+" "+r+" "+ans);
return ans;
}
}
3.树状数组:
用C(i)表示小于等于i的数的个数,add表示把ai加入C中,更改大于等于ai的C
则求逆序对的算法伪代码如下:
PS:当序列中的数特别大时,需要使用离散化,后期补充