逆序數的求法總結(歸並、線段樹、樹狀數組、離散化)


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 }
View Code

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 }
View Code

 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 } 
View Code

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 } 
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM