• 树状数组学习笔记


    前置知识: 前缀和。

    前缀和即:用

    [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;
    }
    
    
    简易的代码胜过复杂的说教。
  • 相关阅读:
    好记性不如烂笔头-linux学习笔记2kickstart自动化安装和cacti
    好记性不如烂笔头-linux学习笔记1
    关于TP5中的依赖注入和容器和facade
    vbs 脚本2
    vbs脚本
    Memcache 和 Radis 比较
    MongoDB 索引的使用, 管理 和优化
    mysql大数据高并发处理
    sql处理高并发
    LB 负载均衡的层次结构
  • 原文地址:https://www.cnblogs.com/bifanwen/p/12498948.html
Copyright © 2020-2023  润新知