逆序数的求法总结(归并、线段树、树状数组、离散化)
1、归并排序求逆序数
http://acm.nyist.net/JudgeOnline/problem.php?pid=117
在归并排序的过程中,比较关键的是通过递归,将两个已经排好序的数组合并,此时,若a[i] > a[j],则i到m之间的数都大于a[j],合并时a[j]插到了a[i]之前,此时也就产生的m-i+1个逆序数,而小于等于的情况并不会产生。
1 #include<stdio.h> 2 #define maxn 1000005 3 int a[maxn],temp[maxn]; 4 long long sum; 5 void merge(int l,int r,int m){ 6 int i = l, j = m + 1,k = l; 7 while(i<=m&&j<=r){ 8 if(a[i] > a[j]){ 9 sum += m - i + 1; 10 temp[k++] = a[j++]; 11 }else{ 12 temp[k++] = a[i++]; 13 } 14 } 15 while(i<=m) 16 temp[k++] = a[i++]; 17 while(j<=r) 18 temp[k++] = a[j++]; 19 for(i = l;i<=r;i++) 20 a[i] = temp[i]; 21 } 22 void mergesort(int l,int r){ 23 if(l<r){ 24 int m = (l + r) / 2; 25 mergesort(l,m); 26 mergesort(m+1,r); 27 merge(l,r,m); 28 } 29 } 30 int main(){ 31 int t; 32 scanf("%d",&t); 33 while(t--){ 34 int n; 35 scanf("%d",&n); 36 for(int i = 0;i < n;i++){ 37 scanf("%d",&a[i]); 38 } 39 sum = 0; 40 mergesort(0,n-1); 41 printf("%lld ",sum); 42 } 43 return 0; 44 }
2、线段树求逆序数
用线段树来求逆序数的思路关键在于,线段树是维护一个区间的,所以,对于这种连续区间求逆序数,完全可以判断当插入一个新的数字时,若比它大的数字已经插入了,说明排在了它的前面,也就是产生了这些逆序数。
这道题还有一个问题就是,当这个数列前面几个数移到后面后这个数列的逆序数将怎样变化,这里我引用博主ACdreamer的话来解释,原博:http://blog.****.net/ACdreamers/article/details/8577553
若abcde...的逆序数为k,那么bcde...a的逆序数是多少?我们假设abcde...中小于a的个数为t , 那么大于a的个数就是n-t-1,当把a移动最右位时,原来比a
大的现在都成了a的逆序对,即逆序数增加n-t-1,但是原来比a小的构成逆序对的数,现在都变成了顺序,因此逆序对减少t ,所以新序列的逆序数为 k +=
n - t - t -1,即k += n-1-2 * t , 于是我们只要不断移位(n次),然后更新最小值就可以了
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #define maxn 5005 5 using namespace std; 6 int tree[maxn<<2],n,k; 7 int t[maxn];//存储输入的数列 8 int sum; 9 void init(){ 10 k = 1; 11 while(k<n) 12 k <<= 1; 13 memset(tree,0,sizeof(tree)); 14 } 15 void upgrade(int index){ 16 index = k + index -1; 17 tree[index]++; 18 index /= 2; 19 while(index){ 20 tree[index] = tree[index*2] + tree[index*2+1]; 21 index /= 2; 22 } 23 } 24 void query(int a,int b,int index,int l,int r){ 25 if(a<=l&&b>=r){ 26 sum += tree[index]; 27 return; 28 } 29 int m = (l+r) / 2; 30 if(a<=m) 31 query(a,b,index*2,l,m); 32 if(b>m) 33 query(a,b,index*2+1,m+1,r); 34 } 35 int main(){ 36 while(scanf("%d",&n)!=EOF){ 37 init(); 38 sum = 0; 39 //求出初始数列的逆序数 40 //由于线段树建立是,根节点是1,维护的区间是1到n,所以读入的每一个数更新到线段树里时都要加一 41 for(int i = 0;i<n;i++){ 42 scanf("%d",&t[i]); 43 query(t[i]+1,n,1,1,k); 44 upgrade(t[i]+1); 45 } 46 int ans = sum; 47 for(int i = 0;i<n;i++){ 48 sum += n-2*t[i]-1; 49 ans = min(ans,sum); 50 } 51 printf("%d ",ans); 52 } 53 return 0; 54 }