树状数组学习笔记

发布时间 2023-08-08 17:57:42作者: fze

树状数组作为一个常数小且好写的数据结构,虽然功能没有线段树那么齐全,但是其中的扩展内容还是很多的。

维护区间和

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;
}

1.3 二维树状数组