zoukankan      html  css  js  c++  java
  • 逆序数的求法总结(归并、线段树、树状数组、离散化)

    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
    ",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
    ",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
    ",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
  • 相关阅读:
    CH和CN图标去除及语言栏变短
    vim命令学习总结
    《Applications=Code+Markup》读书札记(2)——创建一个简单的 WPF 程序的代码结构及关于 Window 实例位置设置问题
    linux 开机时自动挂载分区
    Windows/linux双系统的时间修改问题
    关于RHEL6.1你可能想知道的那点事
    C语言的重要概念
    sprintf你知道多少
    《Applications=Code+Markup》读书札记(1)——一个简单的 WPF 程序
    Linux命令——tar
  • 原文地址:https://www.cnblogs.com/zqy123/p/5017087.html
Copyright © 2011-2022 走看看