树状数组
回顾一下以前不太明白的树状数组原理。以 @Gcint-since2024 大佬做的总结为参考。
- \(lowbit(x)\) 表示 \(x\) 在二进制表示下从右往左第一个 \(1\) 及其后所有的 \(0\) 构成的数。
记 \(a[x]\) 为原数组,\(tree[x]\) 为树状数组:
-
定义 \(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)\) 为例跑一下) -
\(tree[x]\) 有 \(log_2 lowbit(x)+1\) 个子节点,其中一个是叶子结点 \(a[x]\),其他的为 \(tree[x-2^i](i=0,1,2,...,log_2 lowbit(x)-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));
}
}
以上代码为树状数组基本应用,单点修改区间查询。
然后也可以通过维护差分数组,实现区间修改单点查询,代码略。
留坑:高维树状数组。。