树状数组作为一个常数小且好写的数据结构,虽然功能没有线段树那么齐全,但是其中的扩展内容还是很多的。
维护区间和
1.0 BIT 的作用
树状数组可以做到单次 logn 求前缀和,单次 logn 修改信息维护一个前缀和。
1.1 区间修改 单点查询
考虑维护差分数组 \(c[i]=a[i]-a[i-1]\) 。
在查询的部分,一个点的值 \(a[i]\) 将等于 \(\sum\limits_{j=1}^i{c[j]}\) 的值,也就是求前缀和。
修改的话,由于我们要维护的是前缀和,对于一个差分数组来说,显然等价于给 \(l\) 上的点加上 \(v\) , 给 \(r+1\) 上的点加上 \(-v\)。
1.2 区间修改 区间查询
这个部分需要使用两个 BIT 去维护了,设 \(r\) 表示右端点。
首先,由于加法满足结合率,所以可以将区间转化为两端点前缀和之差。
单点的值仍然满足 \(a[i]=\sum\limits_{j=1}^i{c[j]}\) ,因为要求 \(\sum{a[i]}\) ,所以原式子可以写为 \(\sum\limits_{i=1}^r\sum\limits_{j=1}^i{c[j]}\) 。
观察这个式子,不难发现每个 \(c[j]\) 出现了 \(r-j+1\) 次,则原式子又可以写成 \(\sum\limits_{i=1}^rc[i]\times(r+1)-\sum\limits_{i=1}^rc[i]\times i\) 。
因此我们需要开两个 BIT 去单独维护 \(c[i]\) 和 \(c[i]\times i\) 。
第一个好维护,第二个就将 \(l\) 上的点加上 \(v\times l\) , 给 \(r+1\) 上的点加上 \(-v\times(r+1)\) 。
这里给出【例题1】代码
#include<bits/stdc++.h>
#define int long long
#define lowbit(i) i&-i
using namespace std;
const int N=1e5+5;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9')
f=(ch!='-'),ch=getchar();
while(ch<='9'&&ch>='0')
x=(x*10)+(ch^48),ch=getchar();
return f?x:-x;
}
int t1[N],t2[N];
int n,m,op,l,r,k,a[N];
void add(int p,int v)
{
for(int i=p;i<=n;i+=lowbit(i))
t1[i]+=v,t2[i]+=v*p;
}
int ask(int p)
{
int ans=0;
for(int i=p;i;i-=lowbit(i))
ans+=t1[i]*(p+1)-t2[i];
return ans;
}
signed main()
{
n=read(),m=read();
for(int i=1;i<=n;++i)
a[i]=read(),add(i,a[i]-a[i-1]);
while(m--)
{
op=read(),l=read(),r=read();
if(op==1)
{
k=read();
add(l,k),add(r+1,-k);
}
if(op==2)
printf("%lld\n",ask(r)-ask(l-1));
}
return 0;
}