刚学完树状数组,为了防止自己忘了,写个博客记录下
树状数组的单点修改和区间查询的时间复杂度均为 O(log n)
一.前置知识 lowbit()运算
lowbit() 运算是某个数的二进制表示最低为1 的位和它后面的0构成的数值
例如
得出的过程如下
先按位取反 ~ 再+1
得到的数再进行按位与 & 操作
&
=
=
由于计算机存储时候用的是补码,所以取反(~)再加一(++)后的数实际就是这个数的倒数本身
所以lowbit(n) = n & (~n+1) = n & (-n)
int lowbit(int k){
return k & -k;
}
二.树状数组的思想
1.理解
例 给出一个长度为n的数组,进行以下两种操作
第x个数加上k
输出区间[ x , y ]内每个数的和
用前缀和
树状数组维护(
)
建立如图所示的t[x]保存以x为根的子树中叶节点的值的和
每个节点覆盖的长度与t[x]中x有关 即len = lowbit(x)
继续观察 t[x]的父节点为t[x+lowbit(x)] 且 整棵树的深度为
2.add(x,k)操作
在a的第x个数加k
例如,要给a[3]加k,与之有关联的是t[3],t[4],t[8] 即
所以t[3] t[4] t[8]依次加k
void add(int x,int k){
while(x<=n){
t[x]+=k;
x+=lowbit(x);
}
}
3.ask(x)操作
即找x对应的点的前缀和
需要找每个点和它左上角的点依次加和
例如找 ask(7)需要 t[7] t[6] t[4] 加和
int ask(int x){
int ans = 0;
while(x!=0){
ans+=t[x];
x-=lowbit(x);
}
return ans;
}
这个是求前缀和的操作,如果要求区间和 ,可以求出前缀和再相减
(以上为单点增加 范围查询,对应的题目如下)
【模板】树状数组 1 - 洛谷https://www.luogu.com.cn/problem/P3374
//P3374 【模板】树状数组 1
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 50;
int n,m;
int tree[Maxn+5];
int lowbit(int k){
return k & -k;
}
void add(int x,int k){
while(x<=n){
tree[x]+=k;
x+=lowbit(x);
}
}
int sum(int x){
int ans1 = 0;
while(x!=0){
ans1+=tree[x];
x-=lowbit(x);
}
return ans1;
}
int main(){
cin>>n>>m;//数字个数 操作次数
//从a[1]开始输入
for(int i=1;i<=n;i++){
int a;
cin>>a;
add(i,a);
}
//构造树状数组
int choose,x,y;
for(int i=0;i<m;i++){
cin>>choose>>x>>y;
if(choose==1){
add(x,y);
}else if(choose==2){
cout<<sum(y)-sum(x-1)<<endl;
}
}
return 0;
}
三.树状数组的扩展及应用
树状数组动态维护前缀和
工具
灵活应用
区间修改 单点查询
区间修改 区间查询
都可以做
(1)区间修改
在[l,r]+d 这个区间内加d
需要用到差分的思想
()
还可以表示为 ,即num[i]的值可以理解为diff[i]从开始加到第i个值的和
所以对于某个区间内的元素修改,只需要修改两个差分数组的点即可
所以要对树状数组进行操作
从第 个到最后每个加
,在加完之后再从第
个每个向后减
便相当于从第 到第
个每个加
(2)单点查询
然后再按照差分数组的定义 进行求
P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P3368
区间修改/单点查询对应的题目练习
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
ll n, q, mod, x, y, s, inn[500001], tree[500001];
ll lowbit(ll num) {
return num & -num; // 返回值为二进制下num从左往右第一个1的位置
}
void add(ll s, ll num) {
for(ll i = s; i <= n; i += lowbit(i)) tree[i] += num; // 差分的思想
}
ll ask(ll s) {
ll ans = 0;
for(ll i = s; i >= 1; i -= lowbit(i)) ans += tree[i]; // 寻找差分的标记
return ans;
}
int main() {
cin >> n >> q;
for(int i = 1; i <= n; i++) cin >> inn[i];
for(int i = 1; i <= q; i++) {
cin >> mod; // 输入1或2
if(mod == 1) {
cin >> x >> y >> s;
add(x, s);
add(y + 1, -s);
}
if(mod == 2) {
cin >> x;
cout << inn[x] + ask(x) << endl; // 区间查询则为右边界前缀和减去左边界前缀和
}
}
return 0;
}