逆序數


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 }

 


免責聲明!

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



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