zoukankan      html  css  js  c++  java
  • 51nod1019逆序数(归并排序/树状数组)

    题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1019

    题意:中文题诶~

    思路:

    方法1:归并排序~

    归并排序过程为,先不断二分直至每组元素数目为一,此时我们可以将每组元素看做已排序状态;然后在回溯过程把这些组两两合并,并在合并过程中排序;

    那么我们每一次合并都得到已排序的组,直至合并为一个组,即已排序数组;

    那我们如何用上述过程统计逆序对数目呢~这就需要分析一下合并的具体过程啦;

    假设我们现在有两个已排序数组a[p, m], a[q, y] 依次比较a[p], a[q],将其中较小的存入临时数组t中,那么最终得到的t数组就是a[p, y]区间所有元素的已排序状态啦,然后将t数组覆盖a[p, y]数组,那么a[p, y]就是已排序状态了啦~  发现了有木有,如果我们在某一次操作中将右边的元素a[q]存入t中,那么a[q]大于当前左边数组a[p, m]的所有元素,即a[q]与数组a[p, m]中每个元素都能组成一个逆序对,对于a[q]元素我们可以得到(m-p)个逆序对 (数组a[p, m]区间为左闭右开即为区间

    [p, m));对于数组a[p, m], a[q, y],假设元素gg, jj可以组成逆序对,我们可以将区间[p, y)里面可以组成逆序对的情况分为gg, jj都在 左边区间或者右边区间,gg, jj分别位于两个区间中,不过我们不难发现前两种情况就是合并前的第三种情况,例如对于数组a[p, m] 和a[q, y]我们求出gg, jj分处于两边的逆序对数为ans, 那么在下一次合并过程中,对于a[p', p] , a[p, q],则ans为gg, jj都位于a[p, q]中的逆序对数; 从这里我们可以发现虽然我们将逆序岁数分三种情况,事实上我们只要累计第三中情况的逆序对数就好啦~

    代码:

     1 #include <bits/stdc++.h>
     2 #define MAXN 50010
     3 using namespace std;
     4 
     5 int ans=0;
     6 
     7 void teger_sort(int* a, int* t, int x, int y){
     8     if(y-x>1){  //**递归至单个元素为一组
     9         int m=(y+x)>>1;
    10         int p=x, q=m, i=x;
    11         teger_sort(a, t, x, m);  //***左递归
    12         teger_sort(a, t, m, y);  //***右递归
    13         while(p<m||q<y){   //**合并
    14             if(q>=y||(p<m&&a[p]<=a[q])){
    15                 t[i++]=a[p++];  //**将左边元素复制到临时数组
    16             }else{
    17                 t[i++]=a[q++]; //**将右边元素复制到临时数组
    18                 ans+=m-p; //**累计逆序对的数目
    19             }
    20         }
    21         for(int i=x; i<y; i++){ //**将临时数组里已排序的元素还原到原数组
    22             a[i]=t[i];
    23         }
    24     }
    25 }
    26 
    27 int main(void){
    28     int n, a[MAXN], t[MAXN];
    29     scanf("%d", &n);
    30     for(int i=0; i<n; i++){
    31         scanf("%d", &a[i]);
    32     }
    33     teger_sort(a, t, 0, n);
    34     printf("%d
    ", ans);
    35     return 0;
    36 }

    方法2:树状数组~

    首先我们先建立一个数轴,对于输入的数据x,我们给数轴上的x标记1(初始时标记都为0啦~),数轴上x前面的数都比x小,也就是说x前面的所有标记的数都是不可以与x组成逆序对的数,假设x是第i个输入,用sum(x)表示x以及前面所有标记的和,即sum=x前面i-1个元素中比x小的个数+1(因为x本身不能和自己组成逆序对嘛),那么i-sum(x)就是x和其前面的元素可以组成的逆序对数咯,累加所有元素和其前面的元素组成的逆序对数就是我们要的答案啦~
    对于这里的数轴我们可以直接用数组标记就好了,不过这里的x数据范围是1e9,直接开数组肯定开不下啦,用map会超时,所以我们需要对输入的数据先hash一下~
    可是这个思路如果直接暴力的话还是会超时,不过,求sum(x)就是区间求和嘛,区间求和我们可以用树状数组或者线段树嘛,这里树状数组和线段树的效果一样,我们就用代码更简单的树状数组啦;要讲树状数组的话比较麻烦,这里就给出一个本人觉得讲的不错的树状数组博客好了~
    http://blog.csdn.net/ljd4305/article/details/10101535
     
    代码:
     
     1 #include <bits/stdc++.h>
     2 #define MAXN 50010
     3 using namespace std;
     4 
     5 int tree[MAXN], n;
     6 
     7 int lowbit(int x){ //**得到pow(2, k), 其中k为x的二进制末尾0的个数即x二进制最低位1的值
     8     return x&(-x);
     9 }
    10 
    11 void update(int x, int d){ //**向上更新父节点及根节点~
    12     while(x<=n){
    13         tree[x]+=d;
    14         x+=lowbit(x);
    15     }
    16 }
    17 
    18 int sum(int x){ //**向下求和
    19     int ans=0;
    20     while(x>0){
    21         ans+=tree[x];
    22         x-=lowbit(x);
    23     }
    24     return ans;
    25 }
    26 
    27 int main(void){
    28     pair<int, int> p[MAXN];
    29     int gg[MAXN], ans=0;
    30     scanf("%d", &n);
    31     for(int i=1; i<=n; i++){  //**hash
    32         scanf("%d", &p[i].first);
    33         p[i].second=i
    34     }
    35     sort(p+1, p+n+1);
    36     for(int i=1; i<=n; i++){
    37         gg[p[i].second]=i;
    38     }
    39     for(int i=1; i<=n; i++){
    40         update(gg[i], 1); //因为x不能和自己组成逆序对,要减去,所以我们要先更新再求和,更新后sum(x)就包括了x了嘛~
    41         ans+=i-sum(gg[i]);
    42     }
    43     printf("%d
    ", ans);
    44     return 0;
    45 }
     
  • 相关阅读:
    Chrome浏览器M53更新后超链接的dispatchEvent(evt)方法无法触发文件下载
    用es5实现模板字符串
    JS求数组最大值常用方法
    js生成随机数
    常用MouseEvent鼠标事件对象&KeyboardEvent键盘事件对象&常用键盘码
    原生js重写each方法
    indexdb开cai发keng实zhi践lu
    substring和substr的区别和使用
    前端常见面试题总结part2
    前端常见面试题总结1
  • 原文地址:https://www.cnblogs.com/geloutingyu/p/6181096.html
Copyright © 2011-2022 走看看