线段树或树状数组求逆序数

线段树或树状数组求逆序数

         求逆序数的方法有分治,归并,本文只介绍线段树或树状数组求逆序数的办法,众所周知,线段树和树状树可以用来解决区间操作问题,就是因为这两个算法区间操作的时间复杂度很低O(logN),才让这种方法具有可行性。

         首先先来看一个序列   6 1 2 7 3 4 8 5,此序列的逆序数为5+3+1=9。冒泡法可以直接枚举出逆序数,但是时间复杂度太高O(n^2)。冒泡排序的原理是枚举每一个数组,然后找出这个数后面有多少个数是小于这个数的,小于它逆序数+1。仔细想一下,如果我们不用枚举这个数后面的所有数,而是直接得到小于这个数的个数,那么效率将会大大提高。          

         总共有N个数,如何判断第i+1个数到最后一个数之间有多少个数小于第i个数呢?不妨假设有一个区间 [1,N],只需要判断区间[i+1,N]之间有多少个数小于第i个数。如果我们把总区间初始化为0,然后把第i个数之前出现过的数都在相应的区间把它的值定为1,那么问题就转换成了[i+1,N]值的总和。再仔细想一下,区间[1,i]的值+区间[i+1,N]的值=区间[1,N]的值(i已经标记为1),所以区间[i+1,N]值的总和等于N-[1,i]的值!因为总共有N个数,不是比它小就是比它(大或等于)。

        现在问题已经转化成了区间问题,枚举每个数,然后查询这个数前面的区间值的总和,i-[1,i]既为逆序数。

        线段树预处理时间复杂度O(NlogN),N次查询和N次插入的时间复杂度都为O(NlogN),总的时间复杂度O(3*NlogN)

        树状数组不用预处理,N次查询和N次插入的时间复杂度都为O(NlogN),总的时间复杂度O(2*NlogN)

线段树:

[cpp]  view plain  copy
  1. // 线段树  
  2. #include <stdio.h>  
  3. #include <string.h>  
  4. #include <stdlib.h>  
  5. #define MAX 51000  
  6. #define MID(a,b) (a+b)>>1  
  7. #define R(a) (a<<1|1)  
  8. #define L(a) a<<1  
  9. typedef struct {  
  10.     int num,left,right;  
  11. }Node;  
  12. int ans[MAX];  
  13. Node Tree[MAX<<2];  
  14. int n;  
  15.   
  16. void Build(int t,int l,int r)         //以1为根节点建立线段树  
  17. {  
  18.     int mid;  
  19.     Tree[t].left=l,Tree[t].right=r;  
  20.     if(Tree[t].left==Tree[t].right)  
  21.     {  
  22.         Tree[t].num=0;  
  23.         return ;  
  24.     }  
  25.     mid=MID(Tree[t].left,Tree[t].right);  
  26.     Build(L(t),l,mid);  
  27.     Build(R(t),mid+1,r);  
  28. }  
  29.   
  30. void Insert(int t,int l,int r,int x)     //向以1为根节点的区间[l,r]插入数字1  
  31. {  
  32.     int mid;  
  33.     if(Tree[t].left==l&&Tree[t].right==r)  
  34.     {  
  35.         Tree[t].num+=x;  
  36.         return ;  
  37.     }  
  38.     mid=MID(Tree[t].left,Tree[t].right);  
  39.     if(l>mid)  
  40.     {  
  41.         Insert(R(t),l,r,x);  
  42.     }  
  43.     else if(r<=mid)  
  44.     {  
  45.         Insert(L(t),l,r,x);  
  46.     }  
  47.     else  
  48.     {  
  49.         Insert(L(t),l,mid,x);  
  50.         Insert(R(t),mid+1,r,x);  
  51.     }  
  52.     Tree[t].num=Tree[L(t)].num+Tree[R(t)].num;  
  53. }  
  54.   
  55. int Query(int t,int l,int r)           //查询以1为根节点,区间[l,r]的和  
  56. {  
  57.     int mid;  
  58.     if(Tree[t].left==l&&Tree[t].right==r)  
  59.         return Tree[t].num;  
  60.     mid=MID(Tree[t].left,Tree[t].right);  
  61.     if(l>mid)  
  62.     {  
  63.         return Query(R(t),l,r);  
  64.     }  
  65.     else if(r<=mid)  
  66.     {  
  67.         return Query(L(t),l,r);  
  68.     }  
  69.     else  
  70.     {  
  71.         return Query(L(t),l,mid)+Query(R(t),mid+1,r);  
  72.     }  
  73. }  
  74.   
  75.   
  76. int main()  
  77. {  
  78.     int a,n,i,t;  
  79.     scanf("%d",&t);  
  80.     long long int k;  
  81.     while(t--)  
  82.     {  
  83.         scanf("%d",&n);  
  84.         memset(Tree,0,sizeof(Tree));  //初始化线段树  
  85.         Build(1,1,n);  
  86.         for(i=1;i<=n;i++)             //输入n个数  
  87.         {  
  88.             scanf("%d",&ans[i]);  
  89.         }  
  90.         for(i=1,k=0;i<=n;i++)  
  91.         {  
  92.             a=ans[i];  
  93.             Insert(1,a,a,1);          //把线段树[ans[i],ans[i]]区间的值插入为1  
  94.             k=k+(i-Query(1,1,a));     //查询区间[1,ans[i]]值的总和,逆序数等于i-[1,ans[i]]  
  95.         }  
  96.         printf("%lld\n",k);  
  97.     }  
  98.     return 0;  
  99. }  


树状数组:

[cpp]  view plain  copy
  1. // 树状数组  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. #include <string.h>  
  5. #include <algorithm>  
  6. using namespace std;  
  7. #define MAX 100010  
  8. int c[MAX],a[MAX],ans[MAX],n;  
  9.   
  10. int Lowbit(int x)      //返回二进制最后一个1所表示的数  
  11. {  
  12.     return x&(-x);  
  13. }  
  14.   
  15. void Updata(int x)     //向前更新  
  16. {  
  17.     while(x<=n)  
  18.     {  
  19.         c[x]++;  
  20.         x+=Lowbit(x);  
  21.     }  
  22. }  
  23.   
  24. int Sum(int x)         //向后更新求和  
  25. {  
  26.     int sum=0;  
  27.     while(x>0)  
  28.     {  
  29.         sum+=c[x];  
  30.         x-=Lowbit(x);  
  31.     }  
  32.     return sum;  
  33. }  
  34.   
  35. int main()  
  36. {  
  37.     int i,t,k;  
  38.     scanf("%d",&t);  
  39.     while(t--)  
  40.     {  
  41.         scanf("%d",&n);  
  42.         for(i=1;i<=n;i++)  
  43.         {  
  44.             scanf("%d",&ans[i]);  
  45.         }  
  46.         memset(c,0,sizeof(c));        //初始化树状数组  
  47.         for(i=1,k=0;i<=n;i++)  
  48.         {  
  49.             Updata(ans[i]);         //向后更新节点ans[i].k  
  50.             k=k+(i-Sum(ans[i]));    //向前查询节点ans[i].k  
  51.         }  
  52.         printf("%d\n",k);  
  53.     }  
  54.     return 0;  
  55. }  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值