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\n",sum); 42 } 43 return 0; 44 }
2、線段樹求逆序數
用線段樹來求逆序數的思路關鍵在於,線段樹是維護一個區間的,所以,對於這種連續區間求逆序數,完全可以判斷當插入一個新的數字時,若比它大的數字已經插入了,說明排在了它的前面,也就是產生了這些逆序數。
這道題還有一個問題就是,當這個數列前面幾個數移到后面后這個數列的逆序數將怎樣變化,這里我引用博主ACdreamer的話來解釋,原博:http://blog.csdn.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\n",ans); 52 } 53 return 0; 54 }
3、樹狀數組求逆序數
還是上面那道題
由於樹狀數組的特性,求和是從當前節點往前求,所以,這里要查詢插入當前數值之時,要統計有多少個小於該數值的數還沒插入,這些沒插入的數,都會在后面插入,也就形成了逆序數。

1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #define maxn 5005 5 using namespace std; 6 int tree[maxn],t[maxn],n; 7 int lowbit(int n){ 8 return n&(-n); 9 } 10 void add(int index){ 11 while(index<=n){ 12 tree[index]++; 13 index += lowbit(index); 14 } 15 } 16 int sum(int index){ 17 int temp = 0; 18 while(index){ 19 temp += tree[index]; 20 index -= lowbit(index); 21 } 22 return temp; 23 } 24 int main(){ 25 while(scanf("%d",&n)!=EOF){ 26 memset(tree,0,sizeof(tree)); 27 int ans = 0; 28 for(int i = 1;i<=n;i++){ 29 //樹狀數組維護1~n,所以傳參的時候要加1 30 scanf("%d",&t[i]); 31 add(t[i]+1); 32 ans += i- sum(t[i]+1); 33 } 34 int temp = ans; 35 for(int i = 1;i<=n;i++){ 36 ans += n-2*t[i]-1; 37 temp = min(temp,ans); 38 } 39 printf("%d\n",temp); 40 } 41 return 0; 42 }
4、離散化
http://poj.org/problem?id=2299
有些題目給出的數據相差很大,用線段樹或樹狀數組來做可能浪費很大的空間,這時就需要離散化的技巧。由於其他地方都差不太多,所以只大概寫一些離散化的方法。
對於數列9 1 0 5 4來說,他們的下標為1 2 3 4 5,離散化就是把這個數列映射為5 2 1 4 3,最小的數對應1,次小的對應2…,所以先按數值由小到大排序,變為0 1 4 5 9,他們的下標為3 2 5 4 1,先取數值最小的數,它要映射為1,他的下標是index,在aa數組中對應的位置賦值就好了。

1 #include<stdio.h> 2 #include<algorithm> 3 using namespace std; 4 struct node{ 5 int v; 6 int index; 7 }; 8 node a[100]; 9 int aa[100];//離散化后的數組 10 int n; 11 bool cmp(node a,node b){ 12 return a.v < b.v; 13 } 14 int main(){ 15 scanf("%d",&n); 16 for(int i = 1;i<=n;i++){ 17 scanf("%d",&a[i].v); 18 a[i].index = i; 19 } 20 sort(a+1,a+1+n,cmp); 21 for(int i = 0;i<=n;i++) 22 aa[a[i].index] = i; 23 return 0; 24 }