• 树状数组


    好久都对这个东西有点陌生,看了一下午,终于能说出点缘由来了。。

    从网上找的点资料。。。

    一、树状数组是干什么的?

           平常我们会遇到一些对数组进行维护查询的操作,比较常见的如,修改某点的值、求某个区间的和,而这两种恰恰是树状数组的强项!当然,数据规模不大的时候,对于修改某点的值是非常容易的,复杂度是O(1),但是对于求一个区间的和就要扫一遍了,复杂度是O(N),如果实时的对数组进行M次修改或求和,最坏的情况下复杂度是O(M*N),当规模增大后这是划不来的!而树状数组干同样的事复杂度却是O(M*lgN),别小看这个lg,很大的数一lg就很小了,这个学过数学的都知道吧,不需要我说了。

    二、原理讲解


    给定序列(数列)A,我们设一个数组C满足

    C[i] = A[i–2^k+ 1] + … + A[i]

    其中,k为i在二进制下末尾0的个数,i从1开始算!

    则我们称C为树状数组。

    下面的问题是,给定i,如何求2^k?

    答案很简单:2^k=i&(i^(i-1)) ,也就是i&(-i)    为什么呢?? 请看下面:

    整数运算 x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。
           因为:x &(-x) 就是整数x与其相反数(负号取反)的按位与:1&1=1,0&1 =0, 0&0 =1。具体分析如下:
           □ 当x为0时,x&(-x) 即 0 & 0,结果为0;
           □ 当x不为0时,x和-x必有一个为正。不失一般性,设x为正。
           ●当x为奇数时,最后一个比特为1,取反加1没有进位,故x和-x除最后一位外前面的位正好相反,按位与结果为0。最后一位都为1,故结果为       1。
           ●当x为偶数,且为2的m次方(m>0)时,x的二进制表示中只有一位是1(从右往左的第m+1位),其右边有m位0,左边也都是0(个数由表示   x的字        节数决定),故x取反加1后,从右到左第有m个0,第m+1位及其左边全是1。这样,x& (-x) 得到的就是x。 
           ●当x为偶数,却不为2的m次方的形式时,可以写作x= y * (2^k)。其中,y的最低位为1。实际上就是把x用一个奇数左移k位来表示。这时,x的   二进制         表示最右边有k个0,从右往左第k+1位为1。当对x取反时,最右边的k位0变成1,第k+1位变为0;再加1,最右边的k位就又变成了0,第   k+1位因为进         位的关系变成了1。左边的位因为没有进位,正好和x原来对应的位上的值相反。二者按位与,得到:第k+1位上为1,左边右边都为   0。结果为2^k,即         x中包含的2的最大次方的因子。
            总结一下:x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。 比如x=32,其中2的最大次方因子  为                 2^5,故x&(-x)结果为32;当x=28,其中2的最大次方因子为4,故x & (-x)结果为4。当x=24,其中2的最大次方因子为8,故 x&(-x)结果为  8。

    (结合网址:http://blog.csdn.net/int64ago/article/details/7429868)

    三、用途

    (1)最基本的数组区间改变、更新、求和。

    (2)逆序数

    (3)二维线段树求矩阵的更新、求和等(暂未整理)


    四、经典题型(详情:http://blog.csdn.net/zhengxu001/article/details/8029790)


    源代码模板:

    #include<stdio.h>
    #include<string.h>
    #include <iostream>
    using namespace std;
    #define MAX 100
    int a[MAX],c[MAX];
    
    int lowbit(int x){
        return x & (-x);
    }
    
    void update(int x,int add){//更新树状数组   x为原数组的下标  add是原数组的值 
        while(x<MAX){//更新范围,如果数组没全部用到不用全部更新,条件可以设置 
            c[x]+=add;    
            x+=lowbit(x);
        }
    }
    
    int get_sum(int x){//求前n项和 
        int sum=0; 
        while(x!=0){       
            sum+=c[x];   
            x-=lowbit(x);
        }  
        return sum;
    }
    
    int main (){
    	int a[]={0,1,2,3,4,5,6,7,8,9,10};
    	for(int i=1;i<=10;i++)
    	   update(i,a[i]);
    	   
    	for(int i=1;i<=100;i++)
    	   cout<<c[i]<<' ';
    	cout<<endl<<endl;
        for(int i=1;i<=100;i++)  
    	   cout<<get_sum(i)<<' ';
    	return 0;
    }
    


  • 相关阅读:
    Java的jmap命令使用详解
    Linux常用文件管理命令详解
    Java的jps命令使用详解
    快速整明白Redis中的整数集合到底是个啥
    Java的jinfo命令使用详解
    Java的jstack命令使用详解
    快速整明白Redis中的字典到底是个啥
    Java的jstat命令使用详解
    Linux常用系统管理命令详解
    详解ElasticAPM实现微服务的链路追踪(NET)
  • 原文地址:https://www.cnblogs.com/zswbky/p/5431967.html
Copyright © 2020-2023  润新知