逆序數:在一個排列中,如果一對數的前后位置與大小順序相反,
即前面的數大於后面的數,那么它們就稱為一個逆序。
一個排列中逆序的總數就稱為這個排列的逆序數。
逆序數為偶數的排列稱為偶排列;逆序數為奇數的排列稱為奇排列。
{
設 A 為一個有 n 個數字的有序集 (n>1),其中所有數字各不相同。
如果存在正整數 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],則 <A[i], A[j]> 這一個有序對稱為 A 的一個逆序對,也稱作逆序。逆序對的數量稱作逆序數。
例如:數組 <2,3,8,6,1> 的逆序對為:<2,1> <3,1> <8,1> <8,6> <6,1> 共5個逆序對。
對於<2,1>:1 ≤ 1 < 5 ≤ 5 ,A[1] > A[5],所以<A[1],A[5]>為一個合法的逆序對。
目前求逆序對數目比較普遍的方法是利用歸並排序做到的時間復雜度。
當然,也可以利用樹狀數組、線段樹來實現這種基礎功能。復雜度均為。
}
求逆序數方法:
感謝:http://blog.csdn.net/dlengong/article/details/7594919
法一:暴力 O(n^2)
最簡單也是最容易想到的方法就是,對於數列中的每一個數a[i],遍歷數列中的數a[j](其中j<i),若a[i]<a[j],則逆序數加1,
這樣就能統計出該數列的逆序數總和
法二:歸並的思想,利用歸並排序過程
歸並排序的復雜度為O(nlogn),當然此方法的復雜度也為O(nlogn)
有一種排序的方法是歸並排序,歸並排序的主要思想是將整個序列分成兩部分,分別遞歸將這兩部分排好序之后,再和並為一個有序的序列,核心代碼如下
MergeSort(first,last) { If(first==last) return Int med=(first+last)/2; MergeSort(first,med); MergeSort(med+1,last); Merge(first,last); }
在合並的過程中是將兩個相鄰並且有序的序列合並成一個有序序列,如以下兩個有序序列
Seq1:3 4 5
Seq2:2 6 8 9
合並成一個有序序:
Seq:2 3 4 5 6 8 9
對於序列seq1中的某個數a[i],序列seq2中的某個數a[j],如果a[i]<a[j],沒有逆序數,如果a[i]>a[j],那么逆序數為seq1中a[i]后邊元素的個數(包括a[i]),即len1-i+1,
這樣累加每次遞歸過程的逆序數,在完成整個遞歸過程之后,最后的累加和就是逆序的總數
const int LENGTH=100; int temp[LENGTH]; //額外的輔助數組 int count=0; void Merge(int * array,int first,int med,int last) { int i=first,j=med+1; int cur=0; while (i<=med&&j<=last) { if (array[i]<array[j]) { temp[cur++]=array[i++]; } else { temp[cur++]=array[j++]; <span style="color:#ff0000;">count+=med-i+1</span>; //核心代碼,逆序數增加 } } while (i<=med) { temp[cur++]=array[i++]; } while (j<=last) { temp[cur++]=array[j++]; } for (int m=0;m<cur;m++) { array[first+m]=temp[m]; } } void MergeSort(int *array,int first,int last) { if (first==last) { return ; } int med=first+(last-first)/2; MergeSort(array,first,med); MergeSort(array,med+1,last); Merge(array,first,med,last); }
法三:樹狀數組
還是以剛才的序列
3 5 4 8 2 6 9
大體思路為:新建一個數組,將數組中每個元素置0
0 0 0 0 0 0 0
取數列中最大的元素,將該元素所在位置置1
0 0 0 0 0 0 1
統計該位置前放置元素的個數,為0
接着放第二大元素8,將第四個位置置1
0 0 0 1 0 0 1
統計該位置前放置元素的個數,為0
繼續放第三大元素6,將第六個位置置1
0 0 0 1 0 1 1
統計該位置前放置元素的個數,為1
…
…
這樣直到把最小元素放完,累加每次放元素是該元素前邊已放元素的個數,這樣就算出總的逆序數來了
在統計和計算每次放某個元素時,該元素前邊已放元素的個數時如果一個一個地數,那么一趟復雜度為O(n),總共操作n趟,復雜度為O(n^2),和第一種方法的復雜度一樣了,那我們為什么還用這么復雜的方法
當然,在每次統計的過程中用樹狀數組可以把每一趟計數個數的復雜度降為O(logn),這樣整個復雜度就變為O(nlogn)
樹狀數組是一種很好的數據結構,這有一篇專門描述樹狀數組的文章
將序列中的每個數按照從大到小的順序插入到樹狀數組中,給當前插入節點及其父節點的個數加1,然后統計該節點下邊及右邊放置元素的個數
HDU1394
求逆序數
法一:數學 逆序數性質
對於這個題求把第一個數放到最后一個數的最小逆序數,對於原序列而言,如果把第一個數放到最后一個數,逆序列增加n-num[0]+1,逆序列減少num[0].
如何得到的?
#include "iostream" #include "cstdio" using namespace std; #include <stdio.h> int main() { int num[5005],n; while(scanf("%d",&n)!=EOF) { for(int i=0;i<n;i++) scanf("%d",&num[i]); int sum=0,temp; for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(num[i]>num[j]) sum++; temp=sum; for(int i=n-1;i>=0;i--) { temp-=n-1-num[i]; temp+=num[i]; if(temp<sum) sum=temp; } printf("%d\n",sum); } return 0; }
法二:樹狀數組解法
分析:
#include <cstdio> #include <cstring> #include <algorithm> #include <iostream> using namespace std; #define N 5005 #define inf 999999999 int c[N]; int a[N]; int lowbit(int x){ return x&(-x); } void update(int x){ while(x<N){ c[x]++; x+=lowbit(x); } } int get_sum(int x){ int ans=0; while(x>0){ ans+=c[x]; x-=lowbit(x); } return ans; } int main() { int n, i, j, k; while(scanf("%d",&n)==1){ memset(c,0,sizeof(c)); int num=0; for(i=1;i<=n;i++){ scanf("%d",&a[i]); a[i]++; num+=get_sum(N-1)-get_sum(a[i]); update(a[i]); } ///以上用樹狀數組求序列a的逆序數 int minh=inf; for(i=1;i<=n;i++) { num=num-(a[i]-1)+(n-a[i]); ///每次由之前的逆序數推出此時的逆序數 minh=min(minh,num); ///更新最小值 } printf("%d\n",minh); } }
兩代碼區別之處就在於求逆序數的方法。
樹狀數組雖然更復雜,但其統計速度更快,選取那種,還得看情況。
可以看看差距
就這個題,3倍?