1.定義
在一個排列中,如果一對數的前后位置與大小順序相反,即前面的數大於后面的數,那么它們就稱為一個逆序。一個排列中逆序的總數就稱為這個排列的逆序數。
舉個例子:
標准列是1 2 3 4 5
那么 5 4 3 2 1 的逆序數算法:
看第二個,4之前有一個5,在標准列中5在4的后面,所以記1個 類似的,
第三個 3 之前有 4 5 都是在標准列中3的后面,所以記2個 同樣的,
2 之前有3個,
1之前有4個
將這些數加起來就是逆序數=1+2+3+4=10
再舉一個 2 4 3 1 5
4 之前有0個
3 之前有1個
1 之前有3個
5 之前有0個
所以逆序數就是1+3=4
2.求法
1.朴素方法,兩層循環,時間復雜度(O (n^2))
1 int count=0; 2 for(i=0; i<n-1; i++) 3 { 4 for(j=i+1; j<n; j++) 5 { 6 if(a[i]>a[j]) 7 { 8 count++; 9 } 10 } 11 }
2.歸並排序,時間復雜度(O(n log n))
歸並排序是將數列a[l,h]分成兩半a[l,mid]和a[mid+1,h]分別進行歸並排序,然后再將這兩半合並起來。在合並的過程中(設l<=i<=mid,mid+1<=j<=h),當a[i]<=a[j]時,並不產生逆序數;當a[i]>a[j]時,在前半部分中比a[i]大的數都比a[j]大,將a[j]放在a[i]前面的話,逆序數要加上mid+1-i。因此,可以在歸並排序中的合並過程中計算逆序數.
現在以6 1 7 2為例,我們以7 2這一塊來說,歸並排序中當7進入臨時空間的時候,看看6 1這一塊還剩下幾個元素沒有入臨時空間,剩下的元素必定比7大,剩下的元素個數就是由於7產生的逆序對數目,同樣地1產生的逆序對數目類似統計,同樣,由於不同塊之間互不影響,遞歸解決此問題。說的有點亂,但仔細想想是這個道理!!!!!
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define maxn 500010 5 #define ll long long int 6 using namespace std; 7 ll a[maxn]; 8 ll temp[maxn]; 9 ll sum; 10 void Merge(int l,int r,int m) 11 { 12 int i=l; 13 int j = m + 1; 14 int k = l; 15 while(i<=m&&j<=r) 16 { 17 if(a[i]>a[j]) 18 { 19 sum+=m-i+1;///剩下的沒有進入臨時空間的元素的個數 20 temp[k++]=a[j++]; 21 } 22 else 23 { 24 temp[k++]=a[i++]; 25 } 26 } 27 while(i<=m)///將剩余的元素存到數組中 28 { 29 temp[k++]=a[i++]; 30 } 31 while(j<=r) 32 { 33 temp[k++]=a[j++]; 34 } 35 for(i=l; i<=r; i++) 36 { 37 a[i]=temp[i]; 38 } 39 } 40 void mergesort(int l,int r) 41 { 42 if(l<r) 43 { 44 int m = (l + r) / 2; 45 mergesort(l,m);///左二分排序 46 mergesort(m+1,r);///右二分排序 47 Merge(l,r,m);///合並兩個升序數組 48 } 49 } 50 int main() 51 { 52 int n,i; 53 while(scanf("%d",&n)!=EOF) 54 { 55 if(n==0) 56 { 57 break; 58 } 59 for(i=0; i<n; i++) 60 { 61 scanf("%lld",&a[i]); 62 } 63 sum=0; 64 mergesort(0,n-1); 65 printf("%lld ",sum); 66 } 67 return 0; 68 }
3.樹狀數組
由於樹狀數組的特性,求和是從當前節點往前求,所以,這里要查詢插入當前數值之時,要統計有多少個小於該數值的數還沒插入,這些沒插入的數,都會在后面插入,也就形成了逆序數。
假設我們將 序列 6 1 2 7 3 4 8 5 存入數組a【】 中, a【1】=6 , a【2】=1...
那么每次,我們可以將 a【i】 插入到 樹狀數組中,並賦值為 1, 我們求和sum,sum 是1 到 a【i】的和 , 那么這個 sum 表示的值就是當前比a【i】小的數量(包括它本身);而當前一共有 i 個數 , 所以 當前 比a【i】大的數量就是 i - sum;所以 我們統計所有的 i - sum , 它們的和就是逆序數。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 #define LL long long 7 #define N 1005*1005 8 LL ans; 9 int a[N]; 10 int n,c[N]; 11 int lowbit(int x) 12 { 13 return x&-x; 14 } 15 int Getsum(int x) 16 { 17 int ret = 0; 18 while(x>0) 19 { 20 ret+=c[x]; 21 x-=lowbit(x); 22 } 23 return ret; 24 } 25 26 void add(int x,int d) 27 { 28 while(x<=n) 29 { 30 c[x]+=d; 31 x+=lowbit(x); 32 } 33 } 34 int main() 35 { 36 int i,j,k,l,r,t; 37 scanf("%d",&n); 38 memset(c,0,sizeof(c)); 39 for(i = 1; i<=n; i++) 40 { 41 scanf("%d",&a[i]); 42 } 43 ans = 0; 44 for(i = 1; i<=n; i++) 45 { 46 add(a[i],1);///這里將從c[i]賦值為1更像是一種存在,1代表着存在,0不存在 47 ans+=i-Getsum(a[i]); 48 } 49 printf("%lld\n",ans); 50 return 0; 51 }
4.線段樹
用線段樹來求逆序數的思路關鍵在於,線段樹是維護一個區間的,所以,對於這種連續區間求逆序數,完全可以判斷當插入一個新的數字時,若比它大的數字已經插入了,說明排在了它的前面,也就是產生了這些逆序數。其實線段樹與樹狀數組只是兩種不同的數據結構,但在逆序對的處理上二者其實是相同的,樹狀數組有Getsum()函數求1 到 a【i】的和,而線段樹則可以使用Query()來查詢。
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #define MAX 51000 5 #define MID(a,b) (a+b)>>1 6 #define R(a) (a<<1|1) 7 #define L(a) a<<1 8 typedef struct 9 { 10 int num,left,right; 11 } Node; 12 int ans[MAX]; 13 Node Tree[MAX<<2]; 14 int n; 15 void Build(int t,int l,int r) //以1為根節點建立線段樹 16 { 17 int mid; 18 Tree[t].left=l,Tree[t].right=r; 19 if(Tree[t].left==Tree[t].right) 20 { 21 Tree[t].num=0; 22 return ; 23 } 24 mid=MID(Tree[t].left,Tree[t].right); 25 Build(L(t),l,mid); 26 Build(R(t),mid+1,r); 27 } 28 29 void Insert(int t,int l,int r,int x) //向以1為根節點的區間[l,r]插入數字1 30 { 31 int mid; 32 if(Tree[t].left==l&&Tree[t].right==r) 33 { 34 Tree[t].num+=x; 35 return ; 36 } 37 mid=MID(Tree[t].left,Tree[t].right); 38 if(l>mid) 39 { 40 Insert(R(t),l,r,x); 41 } 42 else if(r<=mid) 43 { 44 Insert(L(t),l,r,x); 45 } 46 else 47 { 48 Insert(L(t),l,mid,x); 49 Insert(R(t),mid+1,r,x); 50 } 51 Tree[t].num=Tree[L(t)].num+Tree[R(t)].num; 52 } 53 int Query(int t,int l,int r) //查詢以1為根節點,區間[l,r]的和 54 { 55 int mid; 56 if(Tree[t].left==l&&Tree[t].right==r) 57 return Tree[t].num; 58 mid=MID(Tree[t].left,Tree[t].right); 59 if(l>mid) 60 { 61 return Query(R(t),l,r); 62 } 63 else if(r<=mid) 64 { 65 return Query(L(t),l,r); 66 } 67 else 68 { 69 return Query(L(t),l,mid)+Query(R(t),mid+1,r); 70 } 71 } 72 int main() 73 { 74 int a,n,i,t; 75 scanf("%d",&t); 76 long long int k; 77 while(t--) 78 { 79 scanf("%d",&n); 80 memset(Tree,0,sizeof(Tree)); //初始化線段樹 81 Build(1,1,n); 82 for(i=1; i<=n; i++) //輸入n個數 83 { 84 scanf("%d",&ans[i]); 85 } 86 for(i=1,k=0; i<=n; i++) 87 { 88 a=ans[i]; 89 Insert(1,a,a,1); //把線段樹[ans[i],ans[i]]區間的值插入為1 90 k=k+(i-Query(1,1,a)); //查詢區間[1,ans[i]]值的總和,逆序數等於i-[1,ans[i]] 91 } 92 printf("%lld\n",k); 93 } 94 return 0; 95 }
