树状数组学习笔记

先来道题:

很简单,是吧(有脑子就行)
伪代码如下:

时间复杂度:\(\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

练习

1
2
3

posted @ 2021-07-06 21:17  离线中……  阅读(39)  评论(0编辑  收藏  举报