1.树状数组简介
树状数组是一种支持 单点修改 和 区间查询 的,代码量小的数据结构。
2.为什么要使用树状数组
事实上,树状数组能解决的问题是线段树能解决的问题的子集:树状数组能做的,线段树一定能做;线段树能做的,树状数组不一定可以。虽然大多数能使用树状数组能写的题都能用线段树来写,但是树状数组的代码更加简单直接,所以学习树状数组能大大提高编写代码效率。
3.树状数组基础函数
lowbit函数
顾名思义,lowbit就是二进制下最低位1及后面的零所代表的数,例如5的lowbit是101的最低位,也就是1,即lowbit(5)=1。同时x的管辖区间是从x本身位置往前lowbit(x),如6的管辖区间是[5,6],长度为二进制下110的最低位1代表的数2,即lowbit(6)=2。
以下是lowbit函数的代码
int lowbit(int x)
{
return x&-x;
}
由于作者精力有限,想了解具体原理的读者可以去查找相关资料,就不过多赘述了。
add函数
此函数一般用来建立树状数组,代码如下
void add(int x,int y)
{
while(x <= n){
tree[x] += y;
x += lowbit(x);
}
}
search函数(也可称作getsum函数)
此函数一般用来输出答案时使用,代码如下
int search(int x)
{
int sum = 0;
while(x){
sum += tree[x];
x -= lowbit(x);
}
return sum;
}
以上即树状数组基本所需函数
4.树状数组模版题
代码如下
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2000010;
ll tree[N];
int n,m;
int lowbit(int x)
{
return x & -x;
}
void add(int x,int k)
{
while(x <= n)
{
tree[x] += k;
x += lowbit(x);
}
}
ll getsum(int x)
{
ll sum = 0;
while(x!=0)
{
sum += tree[x];
x -= lowbit(x);
}
return sum;
}
int main()
{
cin >> n >> m;
for(int i = 1 ; i <= n ; i++)
{
int a;
cin >> a;
add(i,a);
}
int opt,x,y;
for(int i = 1 ; i <= m ; i++)
{
cin >> opt >> x >> y;
if(opt == 1)//加上数
{
add(x,y);
}
else
{
cout << getsum(y) - getsum(x - 1) << endl;
}
}
return 0;
}
代码如下
#include<bits/stdc++.h>
using namespace std;
#define mymin -0x7fffffff
#define ll long long
#define R register int
const int INF = 2139063143;
int gcd(int a, int b)
{
if (b == 0)
{
return a;
}
return gcd(b, a % b);
}
inline long long read(){
long long x=0,f=1;
char ch;
ch=getchar();
while (ch<'0'||ch>'9'){
if (ch='-')f=-1;
ch=getchar();
}
while (ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m;
const int N = 510000;
ll input[N];
ll tree[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int y)
{
while(x <= n){
tree[x] += y;
x += lowbit(x);
}
}
int search(int x)
{
int sum = 0;
while(x){
sum += tree[x];
x -= lowbit(x);
}
return sum;
}
int opt,a,b,c;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin >> n >> m;
for(int i = 1 ; i <= n ; i++)
{
cin >> input[i];
}
for(int i = 1 ; i <= m ; i++)
{
cin >> opt;
if(opt == 1){
cin >> a >> b >> c;
add(a,c);
add(b+1,-c);
}
else{
cin >> a;
cout << input[a] + search(a) << endl;
}
}
//输入完毕
}
5.总结
树状数组是基于前缀和的数据结构,其代码量简洁方便,是解决区间查询和单点修改的方法之一,
虽然树状数组确实能解决一般的题目,但对于一些求区间最值的题目无能为力,所以学习线段树是树状数组的最后一道防线,至于为啥不在这里聊聊线段树,那当然是因为作者也不会,but作者会继续加油的~_~,如果有什么更好的理解欢迎讨论。