树状数组入门小结(c++)

树状数组——南昌理工学院ACM集训队

博主对于数据结构以及算法的学习修行尚浅,写博客更多的是为了日后的巩固与复习,博文中若是出现一些错误欢迎大家指出,O(∩_∩)O!

前言

今天来介绍一下树状数组,作为一种重要的数据结构,它又有怎样的魅力;
提到树状数组,大家都会想到另一种数据结构——线段树
关于线段树的内容,可以去看我的另一篇博客(线段树入门(c++)(≧∇≦)ノ)———>传送门
对于这两种数据结构,就像是一种父与子的关系:树状数组的作用被线段树完全涵盖, 凡是可以使用树状数组解决的问题, 使用线段树一定可以解决, 但是线段树能够解决的问题树状数组未必能够解决;
既然如此,我们为什么还要学习树状数组呢?
作为一种重要的数据结构,树状数组必然有着它自己的优势:
1.树状数组与线段树在复杂度上同级, 但是树状数组的常数明显优于线段树, 其编程复杂度也远小于线段树.
2.代码简洁,容易理解,编程效率高;

简介

传统数组的元素修改和连续元素求和的复杂度分别为O(1)和O(n)。树状数组通过将线性结构转换成伪树状结构(树状结构可以实现跳跃式扫描),使得修改和求和复杂度均为O(lgn),大大提高了整体效率。

对于树状数组的存储,既然是数组,自然只需要数组;
在这里插入图片描述
如图,上方的是树状结构,下面的数组是存储方式,至此,我们该如何通过树状结构构造所需要的树状数组呢?

构造方法

树状数组的构造采取二进制思想;
在这里插入图片描述
假设数组a[1…16],那么查询a[1]+…+a[16]的时间是log级别的,而且是一个动态的数据结构,支持随时修改某个元素的值,复杂度也为log级别。
来观察这个图:
令结点为A1…A16,C1…C16表示前缀和
可以得出
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16
在这里插入图片描述

这里有一个有趣的性质:
设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,
所以很明显:Cn = A(n – 2^k + 1) + … + An
用一个循环,每次对所维护的区间进行修改,之后每次循环减去2^k;
用二进制来看就会变得容易理解;
在这里插入图片描述
修改过程是每次加了个二进制的低位1(101+1 ->110, 110 + 10 -> 1000, 1000 + 1000 -> 10000)
查询过程每次就是去掉了二进制中的低位1(1111 - 1 -> 1110, 1110 - 10 -> 1100, 1100 - 100 -> 1000)
查询过程就是修改过程反过来而已;

而现在的重点是如何取出二进制中的低位1;

树状数组核心——lowbit()

c++与java库中没有这个函数,所以需要自己进行构造;
其中代码也非常简单,仅有一行;

int lowbit(int x){
    return x&(-x);//取出二进制中的低位1
}

既然有了取出低位1的方法,后续的操作也就变的简单起来;

树状数组的常见操作

修改值:

要将第x个元素加上k,不仅要修改tree [ x ],还要修改所有覆盖到x的元素;
在这里插入图片描述
如图,若是要修改元素7,则覆盖的8与16都要进行修改;

void xiu_gai(int x,int k){//第x个元素加k
    while(x<=n){
        tree[x]+=k;//修改
        x+=lowbit(x);//找到下一个覆盖x的元素;
    }
}

求前缀和:

求前缀和即为查询操作,也就是将修改操作反过来而已;

int qiu_he(int x){
    int sum=0;
    while(x){
        sum+=tree[x];
        x-=lowbit(x);
    }
    return sum;
}

求区间和:

都已经会求前缀和了,将区间右减去区间左就行了;

int y,z;
cin>>y>>z;
cout<<qiu_he(z)-qiu_he(y-1)<<endl;

例题

题出自luoguP3374(【模板】树状数组 1)https://www.luogu.com.cn/problem/P3374
题目描述:
在这里插入图片描述
数据范围:
在这里插入图片描述
样例说明:
在这里插入图片描述

典型的树状数组模板题(毕竟题目自己都说了(✿◡‿◡));

要点上面都说的差不多了,直接贴AC代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;

int a[500005];
int tree[500005];
int n,m;

int lowbit(int x){
    return x&(-x);
}

void xiu_gai(int x,int k){
    while(x<=n){
        tree[x]+=k;
        x+=lowbit(x);
    }
}

int qiu_he(int x){
    int sum=0;
    while(x){
        sum+=tree[x];
        x-=lowbit(x);
    }
    return sum;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++){
        xiu_gai(i,a[i]);
    }
    int x,y,z;
    while(m--){
        scanf("%d%d%d",&x,&y,&z);
        if(x==1){
            xiu_gai(y,z);
        }
        else{
            cout<<qiu_he(z)-qiu_he(y-1)<<endl;
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值