树状数组

回顾一下以前不太明白的树状数组原理。以 @Gcint-since2024 大佬做的总结为参考。


  • \(lowbit(x)\) 表示 \(x\) 在二进制表示下从右往左第一个 \(1\) 及其后所有的 \(0\) 构成的数。

image

\(a[x]\) 为原数组,\(tree[x]\) 为树状数组:

  1. 定义 \(tree[x]\) 表示以 \(a[x]\) 结尾,长度为 \(lowbit(x)\) 的一串数的和,即 \(tree[x]=\sum\limits_{i=x-lowbit(x)+1}^{x} a[i]\)
    最重要的一条,树状数组中的 \(tree[x]\) 是从 \(a[x]\) 向前 \(lowbit(x)\) 位,因此若要求 \(1\) ~ \(x\) 这个前缀和,只需每次减去 \(lowbit(x)\),累加每一个 \(tree\) 即可。
    由这个求和过程,我们就可以看到二进制拆分的思想了!(可以以 \(0111(7)\) 为例跑一下)

  2. \(tree[x]\)\(log_2 lowbit(x)+1\) 个子节点,其中一个是叶子结点 \(a[x]\),其他的为 \(tree[x-2^i](i=0,1,2,...,log_2 lowbit(x)-1)\)

这条性质是定义的微观分析。感觉还差点意思,以后再看吧。

  1. \(tree[x]\) 的父节点是 \(tree[x+lowbit(x)]\)

单点修改,通过向父节点的追溯可以高效更新所有包含节点 \(x\)\(tree\)


另外,可以参考一下发明人 Fenwick 的说法:

按照Peter M. Fenwick的说法,正如所有的整数都可以表示成 $2$ 的幂的和,我们也可以把一串序列表示成一系列子序列的和。

#include<bits/stdc++.h>
#define N 500005
using namespace std;
int n,m;
namespace BIT
{
	int c[N];
	void add(int x,int y){for(;x<=n;x+=x&-x) c[x]+=y;}	
	int ask(int x)
	{
		int ans=0;
		for(;x;x-=x&-x) ans+=c[x];
		return ans;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		Fenwick::add(i,x);
	}
	while(m--)
	{
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		if(op==1) BIT::add(x,y);
		else printf("%d\n",BIT::ask(y)-Fenwick::ask(x-1));
	}
}

以上代码为树状数组基本应用,单点修改区间查询。
然后也可以通过维护差分数组,实现区间修改单点查询,代码略。

留坑:高维树状数组。。

posted @ 2025-01-09 11:07  Freshair_qprt  阅读(1)  评论(0编辑  收藏  举报