树状数组入门

树状数组是解决动态前缀和问题的数据结构,大佬们总是把ST表,树状数组,线段树三个东西联系在一起,都是用于解决动态区间问题的方法。

树状数组是2的次方前缀和数组,像树一样,如图所示,每个位置 i 的前缀和长度为 lowbit(i),而每层之间深度所差的区间长度也为lowbit(i),例如要对 t[4] +1,则其后面的包含 t[4] 的 t[8]也进行+1。

因此当我们要求 6的前缀和,因该是6所包含的区间加上6包含区间的前面的区间,即是 t[4] + t[6]。

若求 3 到 6的区间和时,便是 前缀和 6 - 前缀和 2,而 前缀和 6 = t[6] +  t[4] ,所以3 到 6 的区间和便是  t[6] +  t[4] - t[2]       

单点加值:

//包含a点的父亲位置都要加上b
void add(int a,int b){
    for(int i=a;i<=n;i+=lowbit(i))
        t[i]+=b;
}

 前缀和:

//x所包含的区间和 + x区间前面所包含的区间和
//x区间的长度为lowbit(x)
int sum(int x){
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i))
        ans+=t[i];
    return ans;
}

下面就是单点改值和区间和查询的问题

P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 x

  • 求出某区间每一个数的和

输入格式

第一行包含两个正整数 n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:

  • 1 x k 含义:将第 x 个数加上 k

  • 2 x y 含义:输出区间 [x,y]内每个数的和

这样子这道题就变得显而易见了

lowbit(x) 的返回值是x二进制最小位1,所以返回的是x&-x,这里我用宏定义

#include <bits/stdc++.h>
#define rep(i,n,m) for(int i=n;i<=m;++i)
#define per(i,n,m) for(int i=n;i>=m;--i)
#define PII pair<int,int>
#define lowbit(x) x&-x
#define INF 1<<30
using namespace std;
typedef long long ll;
const int N=5e5+10;
int t[N],n,q;
void add(int a,int b){
    //父亲节点都 +b
    for(int i=a;i<=n;i+=lowbit(i))t[i]+=b;
}
int sum(int x){
    int ans=0;
    前面每一段区间和
    for(int i=x;i>0;i-=lowbit(i))ans+=t[i];
    return ans;
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n>>q;
    rep(i,1,n){
        int x;cin>>x;
        add(i,x);
    }
    while(q--){
        int a,b,c;cin>>a>>b>>c;
        if(a==1)
            add(b,c);
        else 
            cout<<sum(c)-sum(b-1)<<endl;
            //前缀和差
    }
    return 0;
}

还有区间改值和单点查询的问题,

P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 x;

  2. 求出某一个数的值。

输入格式

第一行包含两个整数 N、M,分别表示该数列数字的个数和操作的总个数。

第二行包含 N 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 M 行每行包含 22 或 44个整数,表示一个操作,具体如下:

操作 1: 格式:1 x y k 含义:将区间 [x,y] 内每个数加上 k;

操作 2: 格式:2 x 含义:输出第 x 个数的值。

下标i    0  1  2  3  4  5  6  7    

原数值a  0  1  2  1 -2  4  9  0

差分值b  0  1  1 -1 -3  6  5 -9

对于区间改值后的区间和,我们知道可以用差分做,那差分是什么呢,

差分 b[i] 就是 a[i] - a[i-1]  ,而 a[n] 可以用\sum_{i=1}^{n}b[i] 表示(哈哈哈,公式好大个),当区间 [l,r] 内+x时,只需 b[l] +x,b[r+1] -x,即可保证只有区间内值增加

对于该题,我们不妨初始化差分数组为0,然后使次区间改值时对差分数组边界进行两次操作 

最后的单点查询就是改变的差分值加上原数值,即 sum(x) + a[x] 

(以下用t数组代表差分数组,q数组表示原数值数组)

#include <bits/stdc++.h>
#define rep(i,n,m) for(int i=n;i<=m;++i)
#define per(i,n,m) for(int i=n;i>=m;--i)
#define PII pair<int,int>
#define lowbit(x)  x&-x
#define INF 1<<30
using namespace std;
typedef long long ll;
const int N=5e5+10;
int q[N],t[N],n,T;
void add(int a,int b){
    for(int i=a;i<=n;i+=lowbit(i))t[i]+=b;
}
int sum(int x){
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i))ans+=t[i];
    return ans;
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    cin>>n>>T;
    rep(i,1,n)cin>>q[i];
    while(T--){
        int x,a,b,c;cin>>x;
        if(x==1){
            cin>>a>>b>>c;
            //差分边界进行操作
            add(a,c);
            add(b+1,-c);
        }else {
            cin>>a;
            //单点查询值=差分数组前缀和+原数值
            cout<<sum(a)+q[a]<<endl;   
        }
    }
    return 0;
}

对于某些会越界的样例,必要时要int改long long。

今天刚学,有点囫囵吞枣,描述的不太清楚。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

女人只会影响我敲代码的速度QAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值