树状数组学习笔记
先来道题:
很简单,是吧(有脑子就行)
伪代码如下:
时间复杂度:\(\Theta(n)\)
但如果是求\(q\)(\(q<10^5\))次连续和呢?
那这样做肯定TLE了
所以我们用前缀和来做:
比暴力快多了
但如果频繁修改\(a[i]\)呢?
那前缀和肯定炸了
这里,我们引入一种新的算法:树状数组
这是树状数组
我们把它更直观的表示下——
这是原来的树:
我们把它右对齐:
这便是树状数组
再详细点:
(其中绿色为树状数组,红色为原数组)
我们令树状数组为\(C\),原数组为\(a\),利用上图不难发现:
\(
C_1=a_1\\
C_2=a_1+a_2\\
C_3=a_3\\
C_4=a_1+a_2+a_3+a_4\\
C_5=a_5\\
C_6=a_5+a_6\\
C_7=a_7\\
C_8=a_1+a_2+a_3+a_4+a_5+a_6+a_7+a_8
\)
可以发现,这颗树是有规律的:
\(C[i]=a[i-2^k+1]+a[i-2^k+2]+... +a[i];//k为i的二进制中从右往左第一位非0的位置\)
那么问题来了:如何求\(2^k\)?
先给个结论:\(2^k\)=\(i\)&\((-i)\)
咱们来看看Xenny大佬的解释:
于是可以写出代码:
int lowbit(int x)
{
return x&(-x);
}
有了以上基础,便可进入正题了(若不理解则多看几遍)
单点更新,区间查询
上面提到了:\(C[i]=a[i-2^k+1]+a[i-2^k+2]+... +a[i]\)
那么如果我们更新某个\(a[i]\)的值,则会影响到所有包含有\(a[i]\)的\(C数组\)
稍微思考可知:\(a[i]\)包含于\(C[i+2^k]、C[(i+2^k)+2^k]...\)
这便是更新和求和
如果你弄懂了上面的内容,代码应该也很好理解了:
int lowbit(int x)
{
return x&(-x);
}
void updata(int x,int y)//表示将a[x]+y
{
for(int i=x;i<=n;i+=lowbit(i))a[i]+=y;//更新
}
int getsum(int x)//表示a[1~x]的总和
{
int ans=0;
for(int i=x;i>=1;i-=lowbit(i))ans+=a[i];//求和
return ans;
}
所以区间 \((x,y)\) 的值为 \(getsum(y)-getsum(x-1)\)
更新和求和的时间复杂度:\(\Theta(\log n)\)
练习题:
1
2
3
区间更新,单点查询
代码并不会变,只是多了个用差分建树
假设有个\(a\)数组:
我们要把\(3\)~\(6\)的数加\(2\),可直接将\(a[3]+2,a[7]-2\)
则\(a\)变为
这样要求每个值时,则只需让一个变量跑一遍累加即可
伪代码:
a[x]+=z;a[y+1]-=z;
for i:=1 to n do
sum+=a[i],
write(sum,' ');
把差分用到树状数组中便可打出如下代码:
void add(int l,int r,int x)//对l到r的数累加x,差分
{
update(l,x);update(r+1,-x);
}
练习:
1
参考资料
https://zhuanlan.zhihu.com/p/93795692
https://blog.csdn.net/bestsort/article/details/80796531
https://www.cnblogs.com/xenny/p/9739600.html
https://www.luogu.com.cn/blog/BotDand/shu-zhuang-shuo-zu-xue-xi-bi-ji