树状数组~

树状数组

树状数组是一种可以“动态求区间和”的树形数据结构,但并没有真正构造出边和树来,所以是“树状”的。

之前基础算法中有学习过前缀和也可以用来求区间和,但是对于修改后再去求前缀和而言复杂度是O(n)的,而树状数组是log(n)的,这就是对于动态求区间和的一种结构

基础树状数组可以实现对区间和的单点修改(log(n),和树状数组结构相关)和区间查询(log(n)),树状数组所需要的东西非常简单,就一个数组t[N],大小和我们维护的数组大小一样即可

树状数组结构: t i t_i ti = ∑ j = l w o b i t ( i ) + 1 i a j \sum_{j=lwobit(i)+1}^{i} a_j j=lwobit(i)+1iaj

t[i]存储a[]数组中的一段区间的和。定义t[i]存储以i结尾,且区间大小为lowbit(i)的区间的和。即t[i]表示a数组从下标[i-lowbit(i)+1 ~ i]的范围之和。

什么是lowbit(i)?,lowbit(i)表示i的二进制最低位1的数的大小。

举个例子:二进制串lowbit(001010100)就是000000100,lowbit(101001)就是000001。利用二进制特性,可以通过i & -i求得i的最低位1表示的数的大小(因为二进制在计算机中以补码形式存在,-i即i二进制取反加一,例如100100, 取反后变成011011,加一后变成011100,&是位运算操作,1&1 = 1,0 & 1= 0, 0 & 0 = 0。取反加一后可以发现最低位1及其右边所有位数都不变,但是左边全都变成了相反0变1,1变0,&上之后自然只剩下最低位1了。

在这里插入图片描述

  • 熟悉了t数组含义后,我们就来看一下修改和求和这两个操作

单点修改update:

看上图,假如我们修改a[3],让它加上x,我们需要维护t数组中那些值呢,可以看出我们应该修改t3, t4, t8这三个点,因为这三个点的管辖区间都包含3这个节点。

如何去找到t3, t4, t8这三个点,就需要用到刚刚的lowbit操作,这里通过+lowbit操作:

假设修改单点3:

3 + lowbit(3) = 4

4 + lowbit(4) = 8

区间查询getprefix:

对于[L, R],查询该区间的和:可以转化为[1, R] - [1, L-1]的差,类似于前缀和得到一段区间和写法。

那如何得到[1, R]的区间和呢,就是-lowbit操作:

假设求sum[1, 5]:

5 - lowbit(5) = 4

4 - lowbit(4) = 0;

到0就终止

这就是为什么树状数组这两个操作都是log(n)的了,因为需要维护t

这里给出这两个操作的具体函数:

//首先是lowbit,就一行
int lowbit(int x) {
    return x & -x;
}

//update k位置增加x
void update(int k, int x) {
    for(int i = k; i <= n; i += lowbit(i)) {
        t[i] += x;
    }
}

//getprefix 求sum[1, k]
int getprefix(int k) {
    int ans = 0;
    for(int i = k; i > 0; i -= lowbit(i)) 
        ans += t[i];
    return ans;
}

例题:

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

在这里插入图片描述

#include<iostream>
#include<vector>
using namespace std;
const int N = 5e5 + 10;

vector<int> a(N), t(N);
int n, m; 
int lowbit(int x) {
 return x & -x;
}
void update(int k, int x) { // 在k位置上a[k]加上x
 for(int i = k; i <= n; i += lowbit(i)) t[i] += x;
}
int getprefix(int k) {
 int ans = 0;
 for(int i = k; i > 0; i -= lowbit(i)) ans += t[i];
 return ans;
}
int main() {
 cin >> n >> m;
 for(int i = 1; i <= n; i++) {
     cin >> a[i];
     update(i, a[i]); // 一开始a[i]数组都为0,每次输入一个数,相当与在原i位置添加一个a[i]
 }
 for(int i = 1; i <= m; i++) {
     int op, x, y;
     cin >> op >> x >> y;
     if(op == 1) update(x, y); //题目意思是在原基础上加上y
     else {
         int ans = getprefix(y) - getprefix(x-1);
         cout << ans << '\n';
     }
 }
 return 0;
}

P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
在这里插入图片描述

这个题和上面那个不同在于对于差分的考察,差分数组求一遍前缀和得到原数组

对于区间[x, y]内每个数都加上k,只需要修改差分数组中两个值,分别是x位置差分数组加k, y+1位置上减k

#include<iostream>
#include<vector>
using namespace std;
using ll = long long;
const int N = 5e5 + 10;

vector<ll> a(N), dif(N), t(N); //这里的t数组里面相当于存的是a的差分数组dif的区间和
int n, m; 
int lowbit(int x) {
 return x & -x;
}
void update(int k, int x) { // 在k位置上dif[k]加上x
 for(int i = k; i <= n; i += lowbit(i)) t[i] += x;
}
ll getprefix(int k) {
 ll ans = 0;
 for(int i = k; i > 0; i -= lowbit(i)) ans += t[i];
 return ans;
}
int main() {
 cin >> n >> m;
 for(int i = 1; i <= n; i++) cin >> a[i];
 dif[0] = 0;
 for(int i = 1; i <= n; i++) {
     dif[i] = a[i] - a[i-1];
     update(i, dif[i]);
 }
 for(int i = 1; i <= m; i++) {
     int op;
     cin >> op;
     if(op == 1) {
         int x, y, k; cin >> x >> y >> k;
         update(y+1, -k);//
         update(x, k);
     }
     else {
         int x; cin >> x; 
         ll ans = getprefix(x);
         cout << ans << '\n';
     }
 }
 return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

幻听嵩的留香

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

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

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

打赏作者

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

抵扣说明:

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

余额充值