树状数组的就相当于局部内前缀和,根据上图来理解,下面的A是树状数组,上面橙色部分是树状数组存的东西,灰线对应下来树状数组的位置,比如说A[2]存的是a[1],a[2]的和,A[9]放的是a[9]的值,修改值的话就是修改灰线对应上去的橙色格子,+k或者-k
那么你们一定很好奇他是怎么求前缀和的,他用到一个lowbit的函数(x & -x),这个原理不用很清楚,只需要知道他可以求二进制的最后一位非0位,比如说,110,得到的是第二位1,也就是10, 1010,也是得到10
举个栗子,想要知道a[1]+a[2]+...a[9]的值,那么先从A[9]开始,得到a[9],接着lowbit(9),9是1001得到1,9-1等于8,下一次加上A[8]的值,8不能继续拆了,到此终止循环,那么a[1]+a[2]+...+a[9]的和就是A[8]+A[9],具体见下面程序
这样的话,求动态数组的前缀和由复杂度O(n²)降为O(nlogn)
树状数组一般来说可以解决(单点修改,区间查询)和(区间修改,单点查询)的问题,以下是两个例题
P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
代码:单点修改,区间查询
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 5e5+10;
int a[N],c[N],n;
int lowbit(int x)
{
return x & -x;
}
void add(int x,int k) // 每一次更新的值是对应该灰线上方的橙色格子
{
for (int i = x; i <= n; i += lowbit(i)) c[i] += k;
}
int sum(int x) // 求c[1]+c[2]+...+c[x]
{
int ret = 0;
for (int i = x; i > 0; i -= lowbit(i)) ret += c[i];
return ret;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
add(i,a[i]);
}
while(m--)
{
int op,x,k,l,r;
cin >> op;
if (op == 1)
{
cin >> x >> k;
add(x,k);
}
else if (op == 2)
{
cin >> l >> r;
cout << sum(r) - sum(l-1) << endl; // 前缀和思想
}
}
return 0;
}
P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
代码:区间修改,单点查询
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 5e5+10;
int a[N],c[N],n;
int lowbit(int x)
{
return x & -x;
}
void add(int x,int k) // 维护一个差分数组
{
for (int i = x; i <= n; i += lowbit(i)) c[i] += k;
}
int sum(int x) // 求c[1]+c[2]+...+c[x]
{
int ret = 0;
for (int i = x; i > 0; i -= lowbit(i)) ret += c[i];
return ret;
}
// 区间修改,单点查询
// 维护一个差分的前缀和即可
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) cin >> a[i];
while(m--)
{
int op,x,y,k;
cin >> op;
if (op == 1)
{
cin >> x >> y >> k;
if (x > y) swap(x,y);
add(x,k); // 差分思想,区间修改可以求差分数组的前缀和
add(y+1,-k);
}
else if (op == 2)
{
cin >> x;
cout << a[x] + sum(x) << endl;
}
}
return 0;
}