逆序数的求法总结(归并、线段树、树状数组、离散化)

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

 

你可能感兴趣的:(逆序数的求法总结(归并、线段树、树状数组、离散化))