一、树状数组是什么
树状数组就是把普通数组改成的长得像树的数组
它长什么样大家可以自行搜索【放不上图片让我很懵】【欢迎私信告诉我怎么放图片】
其中有一部分数是原数组几个数的和
比方说2,就是1和2两个数的和;4,就是1、2、4三个数的和
可以看出这些数都是2的幂
这里要引入一个概念:lowbit
lowbit是指一个数的二进制表示的最后一个1所表示的数值
比方说6,6的二进制为000110
它最后一位1表示的大小为2,即lowbit(6)=2
这个lowbit其实就是树状数组每一个元素所代表的原数组的元素的数量
即树状数组中第6个元素是原数组中2个元素的和
这样的数组,就是树状数组
二、操作
1、单点修改
树状数组的一个元素是原数组中很多元素的和,所以原数组改了一个点,那么树状数组中和这个点有关的所有点都要改
因为a[i+lowbid(i)]这个元素是包含a[i]的【这句话没看懂的可以去看图】
所以如果要改a[i]也要一块把a[i+lowbid(i)]也改了
代码如下
void jia(int x,int k)
{
while(x<=n)
{
t[x]+=k;
x+=lowbit(x);
}
}
2、询问区间和
例如,询问 l 到 r 之间的所有元素的和
通过了解前缀和我们知道s[l~r]=s[l]-s[r-1]
那么同理,询问区间和时我们只需要知道数组中的s[1~l]和s[1~r-1]
又可知f[i]代表的是i及i之前lowbit(i)个元素的和
那么就把沿途的都加上就好了
代码如下
int sum(int x)
{
int s=0;
while(x!=0)
{
s+=t[x];
x-=lowbit(x);
}
return s;
}
3、区间修改
区间修改和下面的单点查询需要用到差值【不然一个一个改会TLE不要问我怎么知道的】
差值就是用一个数组【假设为f】存储a中每个元素的差
即f[i]=a[i]-a[i-1],然后我们再用一个树状数组存储f,就能很快的操作
读入代码如下
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
xiugai(i,a[i]-a[i-1]);
}
对了读入要用scanf和printf,不然洛谷上的模板题会TLE三个点
了解过差值的人应该知道,a[i]=a[i-1]+f[i]
那么如果要修改a[l]~a[r]这个区间,只需要把f[l]+k,f[r+1]-k
这样r之后的元素就会被+k-k抵消掉成原值
树状数组的区间修改就是用了这个原理
代码如下
void xiugai(int x,int k)
{
while(x<=n)
{
f[x]+=k;
x+=lowbit(x);
}
}
int main()
{
scanf("%d %d %d",&y,&z,&b);
xiugai(y,b);
xiugai(z+1,-b);
}
4、单点查询
单点查询就可以利用差值
可以利用前面所说的sum函数也就是求区间和函数
因为a[0]=0,那么易证sum[i]的值就是a[i]的值
输出sum[i]即可
代码如下
int findsum(int x)
{
int s=0;
while(x)
{
s+=f[x];
x-=lowbit(x);
}
return s;
}
int main()
{
scanf("%d",&y);
printf("%d\n",findsum(y));
}
5、总代码
单点修改和区间查询:https://blog.youkuaiyun.com/jkrj02/article/details/81115322
区间修改和单点查询:https://blog.youkuaiyun.com/jkrj02/article/details/81115267