树状数组求逆序数

逆序数就是数中各位在它前面有多少个数比它大,求出这些元素个数之和。

 

今天看了个树状数组,可以很好的解决这个问题,普通方法需要O(N^2)复杂度,用树状数组只需要O(NlongN)

 

树状数组实际上还是一个数组,只不过它的每个元素保存了跟原来数组的一些元素相关的结合值。

若A为原数组,定义数组C为树状数组。C数组中元素C[ i ]表示A[ i –lowbit( i ) + 1]至A[ i ]的结合值。

lowbit(i)是i的二进制中最后一个不为零的位数的2次方,可以这样计算

lowbit(i)=x&(-x)

lowbit(i)=x&(x^(x-1))

 

 

 

当想要查询一个sum(n)时,可以依据如下算法即可:

step1: 令sum = 0,转第二步;
step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步;
step3:  令n = n – lowbit(n),转第二步。


n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。

 

修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。所以修改算法如下(给某个结点i加上x):

step1: 当i > n时,算法结束,否则转第二步;
step2: Ci = Ci + x, i = i + lowbit(i)转第一步。

i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。

 

求逆序的思路:

 

可以把数一个个插入到树状数组中, 每插入一个数, 统计比他小的数的个数,对应的逆序为 i- getsum( data[i] ),其中 i 为当前已经插入的数的个数, getsum( data[i] )为比 data[i] 小的数的个数,i- getsum( data[i] ) 即比 data[i] 大的个数, 即逆序的个数。最后需要把所有逆序数求和,就是在插入的过程中边插入边求和。

 

下面是代码:

 

[cpp]  view plain copy
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. #define  N  10  
  5.   
  6. struct Node{  
  7.     int data;  
  8.     int pos;  
  9. };  
  10.   
  11. Node d[N+1];  
  12. int inverse[N+1];   
  13. int count[N];   
  14.   
  15. int cmp(const void*a,const void*b)  
  16. {  
  17.     Node *pa=(Node*)a;  
  18.     Node *pb=(Node*)b;  
  19.     return pa->data-pb->data;  
  20. }  
  21.   
  22. int lowbit(int t){   
  23.     return t & (t^(t-1));   
  24. }  
  25. void modify(int pos,int num)  
  26. {  
  27.     while (pos<=N) {  
  28.         inverse[pos]+=num;  
  29.         pos+=lowbit(pos);  
  30.     }  
  31. }  
  32. int sum(int end)  
  33. {  
  34.     int sum=0;  
  35.     while (end>0) {  
  36.         sum+=inverse[end];  
  37.         end-=lowbit(end);  
  38.     }  
  39.     return sum;  
  40. }  
  41.   
  42.   
  43. int main()  
  44. {  
  45.     memset(inverse,0,sizeof(inverse)); //初始化  
  46.     memset(count,0,sizeof(count));  
  47.   
  48.     char* a="9854623870";  //长度N  
  49.     for(int i=0;i<strlen(a);i++)  
  50.     {  
  51.         d[i+1].data =a[i]-'0';  
  52.         d[i+1].pos=i+1;  
  53.     }  
  54.       
  55.     qsort(d+1,N,sizeof(Node),cmp);  
  56.       
  57.     int id=1;  
  58.     count[d[1].pos]=1;  
  59.     for(int i=2;i<=N;i++)  
  60.     {  
  61.         if(d[i].data==d[i-1].data)  
  62.             count[d[i].pos]=id;  
  63.         else  
  64.             count[d[i].pos]=++id;  
  65.     }  
  66.     int num=0;  
  67.     for(int i=1;i<=N;i++)  
  68.     {  
  69.         modify(count[i],1);  
  70.         num+=i-sum(count[i]);  
  71.     }  
  72.       
  73.     cout<<num<<endl;  
  74.   
  75.     return 0;  
  76. }  

 

中间用到了排序,需要统计位于下标i处比i小的数,然后在树状数组中计算每个位置的和。

排序复杂度O(nlogn), 计算逆序数和的时候也是O(nlogn).

这里处理的是一个数的不同位,当然可以扩展到很多数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值