• 树状数组学习笔记


    前置知识: 前缀和。

    前缀和即:用

    [s_i = sum_{j=1}^i a_j ]

    然后

    [sum_{i=l}^r a_i = s_r - s_{l-1} ]

    树状数组是基于前缀和的一种数据结构。

    它主要可以维护区间和、单点改、区间改、单点查。

    和线段树相比,码量较少、空间复杂度较少是一大优势;但功能不全(比如区间排名,这权值线段树可以搞,但树状数组无法实现)也是弱点。

    说实话,前人的智慧是无穷的,能想到树状数组这种超能的武器!

    树状数组的原理:

    每一个节点存储以自己结尾的若干节点的和。那么“若干节点”是多少个呢?下面考虑一个 lowbit.

    inline int lowbit(int x) {
    	//求2^{x末尾0的个数} 
    	return x & (-x);
    }
    

    至于它怎么求的,你不用管;只需要知道这是求什么就行,代码中注释了。

    那么,看个图:

    你会发现,如果 (S=lowbit(x))

    此时 (x) 的紧靠着左边的那个节点就是 (x-S)

    紧靠着右边的那个节点就是 (x+S) (这其实也是自己的父亲)

    然后呢,查询 (1) ~ (n) 的和只需要这几步:

    1. 找出 (lowbit(n))的值

    2. 然后 (n) 变为自己紧靠着的左边的那个节点

    3. 如果 (n leq 0) 结束;否则回到第一步。

    (代码中 (c) 数组就是树状数组)

    
    inline ll sum(int x) {
    	//求n的前缀和
    	ll s=0; while(x>0) {
    		s+=c[x];
    		x-=lowbit(x);
    	//这是x节点紧贴着的前一个区间 
    	} return s;
    }
    

    那么,我们需要知道:怎么建树呢?

    如果想建树,不外两种:

    1. 不断单点修改

    2. 强行构树(比如线段树)

    我们要考虑如何单点修改。

    其实不难,做三步:

    1. 找到 (lowbit(n)) 并暴力加

    2. 然后 (n) 变为自己紧靠着的右边的那个节点

    3. 如果 $x > n $ 结束;否则回到第一步。

    这和求和差不多。一个往前,一个往后,异曲同工。

    下面,我们可以解决第一个模板。

    树状数组1

    #pragma GCC optimize(2)
    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=1e6+1;
    typedef long long ll;
    
    inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
    	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
    
    int a[N]; ll c[N];
    int n,m,x,y;
    
    inline int lowbit(int x) {
    	//求2^{x末尾0的个数} 
    	return x & (-x);
    }
    
    inline ll sum(int x) {
    	//求n的前缀和
    	ll s=0; while(x>0) {
    		s+=c[x];
    		x-=lowbit(x);
    	//这是x节点紧贴着的前一个区间 
    	} return s;
    }
    
    inline void update(int x,int k) {
    	a[x]+=k; while(x<=n) {
    		c[x]+=k; //暴力加和 
    		x+=lowbit(x); 
    	//这是x节点紧贴着的后一个区间 
    	}
    }
    
    int main(){
    	n=read(); m=read();
    	for(int i=1;i<=n;i++) {
    		x=read(); update(i,x);
    	} while(m--) {
    		int opt=read(); x=read(),y=read();
    		if(opt==1) update(x,y); //单点修改
    		else printf("%lld
    ",sum(y)-sum(x-1)); //利用前缀和思想
    	}
    	return 0;
    }
    
    
    简易的代码胜过复杂的说教。
  • 相关阅读:
    oracle 导入数据时提示只有 DBA 才能导入由其他 DBA 导出的文件
    oracle 常用语句
    android udp 无法收到数据 (模拟器中)
    android DatagramSocket send 发送数据出错
    AtCoder ABC 128E Roadwork
    AtCoder ABC 128D equeue
    AtCoder ABC 127F Absolute Minima
    AtCoder ABC 127E Cell Distance
    CodeForces 1166E The LCMs Must be Large
    CodeForces 1166D Cute Sequences
  • 原文地址:https://www.cnblogs.com/bifanwen/p/12498948.html
Copyright © 2020-2023  润新知