zoukankan      html  css  js  c++  java
  • 树状数组(Binary Indexed Tree)

    原文博客链接:https://www.cnblogs.com/acgoto/p/8583952.html

    对于学习的线段树方面的,发现很多问题可以用它来求解,但是做题的时候发现,用线段树太容易tle也,很多次也是卡时间过的,然后就发现了树状数组。

    首先我们搞明白树状数组是用来干嘛的,树状数组是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值,经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。

    所以说,树状数组的求解和线段树的求解有些重合,但是功能没有线段树的那么广泛。但是对树状数组来说,其思想大概是用二进制的方法来实现。

    先简单介绍下基本二进制的使用方法:

    & 按位与       如果两个相应的二进制位都为1,则该位的结果值为1,否则为0
    | 按位或        两个相应的二进制位中只要有一个为1,该位的结果值为1
    ^ 按位异或    若参加运算的两个二进制位值相同则为0,否则为1
    ~ 取反           ~是一元运算符,用来对一个二进制数按位取反,即将0变1,将1变0
    << 左移         用来将一个数的各二进制位全部左移N位,右补0  (<<1则是乘2)
    >> 右移         将一个数的各二进制位右移N位,移到右端的低位被舍弃,对于无符号数,高位补0(>>1则是除2)

    原理:lowbit      

     lowbit这个函数的功能就是求某一个数的二进制表示中最低的一位1,举个例子,x = 6,它的二进制为110,那么lowbit(x)就返回2,因为最后一位1表示2

    那么怎么求lowbit呢?把这个数的二进制写出来,然后从右向左找到第一个1(这个1就是我们要求的结果,但是现在表示不出来,后来的操作就是让这个1能表示出来),这个1不要动和这个1右边的二进制不变,左边的二进制依次取反,这样就求出的一个数的补码,说这个方法主要是让我们理解一个负数的补码在二进制上的特征,然后我们把这个负数对应的正数与该负数与运算一下,由于这个1的左边的二进制与正数的原码对应的部分是相反的,所以相与一定都为0,;由于这个1和这个1右边的二进制都是不变的,因此,相与后还是原来的样子,故,这样搞出来的结果就是lowbit(x)的结果。

    自己来理解一下是怎么存的数据到c数组里面

    #include <stdio.h>
    #include <iostream>
    #include <cstdlib>
    #include <cmath>
    #include <cctype>
    #include <string>
    #include <cstring>
    #include <algorithm>
    #include <stack>
    #include <queue>
    #include <set>
    #include <map>
    #include <ctime>
    #include <vector>
    #include <fstream>
    #include <list>
    #include <iomanip>
    #include <numeric>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    #define me0 memset(s,0,sizeof(s))
    #define me1 memese(s,1,sizeof(s))
    const int inf = 0x3f3f3f3f;
    const int N=100005;
    int a[N],b[N],c[N];
    int lowbit(int x){
        return x&(-x);
    }
    void update(int i,int cc,int n){
        while(i<=n){
            c[i]+=cc;
            i+=lowbit(i);
        }
    }
    int main(int argc, char * argv[]) 
    {
        std::ios::sync_with_stdio(false);
        int n;
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            update(i,a[i],n);
        }
        for(int i=1;i<=n;i++) cout<<c[i]<<" ";
        return 0;
    }

    输入:

    5

    1 2 3 4 5

    运行结果:

    1 3 3 10 5

    结合图一起看,

    c[1]=1,

    c[2]=a[1]+a[2]=3

    c[3]=a[3]=3

    c[4]=a[1]+a[2]+a[3]+a[4]=10

    c[5]=a[5]=5

    然后后面的看到大佬的博客就直接贴个博客链接了,感觉大佬讲解的很详细了。

    贴个大佬的模板:

    int lowbit(int i){
        return i & -i;
    } 
    
    //-i 代表i的负数 计算机中负数使用对应的正数的补码来表示
    例如 : i=60110) 此时 k=1
    -i=-6=(1001+1)=(1010)
    i&(-i)=(0010)=2=2^1
    
    void update(int x,int cc,int n){
        for(int i=x;i<=n;i+=lowbit(i)) //x为更新的位置,cc为更新后的数,n为数组最大空间 
        c[i]+=cc;
    }
    
    int sum(int x){  //求区间[1,x]内所有元素的和 
        int ans=0;
        for(int i=x;i;i-=lowbit(i))
        ans+=c[i];  // 从右往左累加求和 
        return ans;
    }

    来题模板题目 hdu1166 

    #include<bits/stdc++.h>
    using namespace std;
    int c[50004]; 
    int lowbit(int i){
        return i&(-i);
    }
    
    void update(int x,int cc,int n){
        for(int i=x;i<=n;i+=lowbit(i)) //x为更新的位置,cc为更新后的数,n为数组最大空间 
        c[i]+=cc;
    }
    
    int sum(int x){  //求区间[1,x]内所有元素的和 
        int ans=0;
        for(int i=x;i;i-=lowbit(i))
        ans+=c[i];  // 从右往左累加求和 
        return ans;
    }
    
    int main(){
        ios::sync_with_stdio(false);
        int t,x,y,a;
        char s[10];
        cin>>t;
        for(int z=1;z<=t;z++){
            memset(c,0,sizeof(c));
            int n,a;
            cin>>n;
            for(int i=1;i<=n;i++){
                cin>>a;
                update(i,a,n);
            }
            cout<<"Case "<<z<<":"<<endl;
            while(1){
                cin>>s;
                if(s[0]=='E') break;
                cin>>x>>y;
                if(s[0]=='A'){
                    update(x,y,n);
                }
                if(s[0]=='S'){
                    update(x,-y,n);
                }
                if(s[0]=='Q'){
                    cout<<sum(y)-sum(x-1)<<endl;
                }
            }
        }
        return 0;
    } 
  • 相关阅读:
    Gitlab安装之后不能正常启动案例解决
    SSH远程采用普通用户登录linux主机报错解决方法
    记一次AD域控客户端不能正常加域的故障处理
    Horizon7.9部署和克隆问题汇总
    VMware Guest customization fails on Linux
    Ubuntu18.04安装rabbitvcs svn图形化客户端和简单实用
    Ubuntu访问samba共享文件方法
    CentOS版本禁用Ctrl+Alt+Del重启功能
    02Kubernetes架构和组件
    01Kubernetes核心概念
  • 原文地址:https://www.cnblogs.com/wushengyang/p/10319111.html
Copyright © 2011-2022 走看看