首先,我们阐明一下为什么我们要学习树状数组,因为这个东西在某些方面来说要比线段树好一些,首先就是他自己比较好写,第二就是他在解决某些简单问题时,时间复杂度会比线段树更小一些。
下面先来上一篇代码:
大家可以认真看一下
题目传送门(单点修改和区间查询)
#include<iostream>
#include<cstring>
#define lowbit(x) x&-x
using namespace std;
int m,n;
long long node[500005];
void add(long long num,int x)
{
while(x<=n)
{
node[x]+=num;
x+=lowbit(x);
}
}
long long query(int x)
{
long long ans;
ans=0;
while(x)
{
ans+=node[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
memset(node,0,sizeof(node));
std::ios::sync_with_stdio(false);
cin>>n>>m;
long long a;
for(int i=1;i<=n;++i)
{
cin>>a;
add(a,i);
}
//int a;
int c,d;
long long e;
for(int i=1;i<=m;++i)
{
cin>>a;
if(a==1)
{
cin>>c>>e;
add(e,c);
//add(-e,d+1);
}
else
{
cin>>c>>d;
cout<<(query(d)-query(c-1))<<endl;
}
}
return 0;
}
那么下面我们就来简单的讲解一下树状数组,代码其实非常的简单,也非常的好理解,唯一不好理解的就是,lowbit和它建立的树的模样,在这里为大家提供一张图和一个实现lowbit操作的简单计算器,大家可以体会一下lowbit的神奇。
代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cout<<"Welcome to use the tool of lowbit"<<endl;
for(;;)
{
cin>>n;
cout<<(n&(-n))<<endl;
cout<<"_________________________________"<<endl;
}
return 0;
}
图片
关键
+lowbit跳到自己的爸爸,-lowbit跳到自己管辖范围的前面(这样就实现了求前缀和,当然你也可以存差分的东西这样就可以存当前位置节点的大小,后面会讲解)
好了相信你在自己做了一些实验后已经感受到了lowbit的神奇,那么下面我们就来证明一下,博主很懒,这里粘一篇感觉写的很好的证明。
首先明白一个概念,计算机中-i=(i的取反+1),也就是i的补码
而lowbit,就是求(树状数组中)一个数二进制的1的最低位,例如01100110,lowbit=00000010;再例如01100000,lowbit=00100000。
所以若一个数(先考虑四位)的二进制为abcd,那么其取反为(1-a)(1-b)(1-c)(1-d),那么其补码为(1-a)(1-b)(1-c)(2-d)。
如果d为1,什么事都没有-_-|||但我们知道如果d为0,天理不容2Σ( ° △ °|||)︴
于是就要进位。如果c也为0,那么1-b又要加1,然后又有可能是1-a……直到碰见一个为补码为0的bit,我们假设这个bit的位置为x
这个时候可以发现:是不是x之前的bit的补码都与其自身不同?,x之后的补码与其自身一样都是0?
例如01101000,反码为10010111,补码为10011000,可以看到在原来数正数第五位前,补码的进位因第五位使其不会受到影响,于是0&1=0,;
但在这个原来数“1”后,所有零的补码都会因加1而进位,导致在这个“1”后所有数都变成0,再加上0&0=0,所以他们运算结果也都是零;
只有在这个数处,0+1=1,连锁反应停止,所以这个数就被确定啦O(∩_∩)O
所以and以后只有x这个bit是一……
下面这里有一道升级版的题目,大家可以看一看。
这里在上代码之前还是要做一番讲解,这个东西需要用到差分的思想,下面是一个关于差分思想的简介:
来介绍一下差分
设数组a[]={1,6,8,5,10},那么差分数组b[]={1,5,2,-3,5}
也就是说b[i]=a[i]-a[i-1];(a[0]=0;),那么a[i]=b[1]+....+b[i];(这个很好证的)。
假如区间[2,4]都加上2的话
a数组变为a[]={1,8,10,7,10},b数组变为b={1,7,2,-3,3};
发现了没有,b数组只有b[2]和b[5]变了,因为区间[2,4]是同时加上2的,所以在区间内b[i]-b[i-1]是不变的.
所以对区间[x,y]进行修改,只用修改b[x]与b[y+1]:
b[x]=b[x]+k;b[y+1]=b[y+1]-k;
相信认真看了上面我写的“关键”你就可以知道这玩意儿怎么做了,下面上标程:
#include<iostream>
#include<cstring>
#define lowbit(x) x&-x
using namespace std;
int m,n;
long long node[500005];
void add(long long num,int x)
{
while(x<=n)
{
node[x]+=num;
x+=lowbit(x);
}
}
long long query(int x)
{
long long ans;
ans=0;
while(x)
{
ans+=node[x];
x-=lowbit(x);
}
return ans;
}
int main()
{
memset(node,0,sizeof(node));
std::ios::sync_with_stdio(false);
cin>>n>>m;
long long a,last;
last=0;
for(int i=1;i<=n;++i)
{
cin>>a;
add(a-last,i);
last=a;
}
//int a;
int c,d;
long long e;
for(int i=1;i<=m;++i)
{
cin>>a;
if(a==1)
{
cin>>c>>d>>e;
add(e,c);
add(-e,d+1);
}
else
{
cin>>c;
cout<<query(c)<<endl;
}
}
return 0;
}
后面还是会更新的······