(一维&二维&三维)树状数组 及其 模板

  • 一维树状数组

学习来自https://www.bilibili.com/video/av18735440?from=search&seid=12510069409139918776电子科技大学算法讲堂

https://www.cnblogs.com/RabbitHu/p/BIT.html大佬的文章,侵删,本文推理都来自该文。


这里d数组是我的树状数组,a数组就是初始数组了,让你修改查询的

这就是树状数组的存储方式,不难看出它的存储是和二进制有关的(滑稽,好看出个鬼)举个栗子d[6]=a[5]+a[6]; (6的二进制数是110,末尾一个0,那d[6]就是存储2^1个元素的和) 同理我们也可以验证d[8]=a[1]+...a[8](因为8的二进制数是1000,有3个零,就是存储2^3个)  ; 而查询前缀和,就需要用到lowbit这个函数,非常巧妙,我说不太清,看视频说的非常清楚。

int lowbit(int x){
	return x&(-x);
} 
x-=lowbit(x);
or x+=lowbit(x);

也可以直接使用 x+=x&(-x) or x-=x&(-x)

比如查询13这个位置的前缀和 13的二进制数为1101,我们将其拆分为(lowbit就是在此起着至关重要的作用,取得最后一个1)

1101                    d[13]               a[13]                  //从后往前以1为末尾,后面全是0

1100                    d[12]               a[9]+a[12]

1000                    d[8]                 a[1]+...a[8]

所以前13的前缀和就是 d[13] +d[12] +d[8] 

而修改和查询的顺序是反的。

这里我搬运了大佬的代码(笑嘻嘻

单点修改+区间查询 

//单点修改+区间查询 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e7+5;
ll sum[maxn],n;//数组从1开始到n 
void add(int p, int x){ //给位置p增加x
    while(p <= n) sum[p] += x, p += p & -p;
}
ll ask(int p){ //求位置p的前缀和
    ll res = 0;
    while(p) res += sum[p], p -= p & -p;
    return res;
}
ll range_ask(int l, int r){ //区间求和
    return ask(r) - ask(l - 1);
} 
int main(){
	return 0;
}

而区间修改的话,其实就是用了一个差分的思想了;

比如 a数组         1  1  1  1  1         

        b数组         1  0  0  0  0        b[n]=a[n]-a[n-1]

我想要再a数组的2~3都加上2;

       a数组          1  3  3  3  1

       b数组          1  2  0  0  -2    我们发现只要修改b数组两个元素就可以来维护a数组了

a数组就是b数组的前缀和

所以区间修改 只要修改俩个元素就行了(舒舒服服)。

区间修改+单点查询

(这里都搬运的大佬的详解)

//区间修改+单点查询 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e7+5;//这里可以理解成 原数组是sum数组的前缀和 
ll sum[maxn],n;//数组从1开始到n 
void add(int p, int x){ //这个函数用来在树状数组中直接修改
    while(p <= n) sum[p] += x, p += p & -p;
}
void range_add(int l, int r, int x){ //给区间[l, r]加上x
    add(l, x), add(r + 1, -x);
}
int ask(int p){ //单点查询
    ll res = 0;
    while(p) res += sum[p], p -= p & -p;
    return res;
}
int main(){
	return 0;
} 

 区间修改+区间查询

//区间修改+区间查询 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
const ll maxn=1e7+5;
ll d[maxn];//记录原数组的前缀和 
ll sum1[maxn];
ll sum2[maxn];
ll n,M;
void add(ll p, ll x){
    for(int i = p; i <= n; i += i & -i)
        sum1[i] += x, sum2[i] += x * p;
}
void range_add(ll l, ll r, ll x){
    add(l, x), add(r + 1, -x);
}
ll ask(ll p){
    ll res = 0;
    for(int i = p; i; i -= i & -i)
        res += (p + 1) * sum1[i] - sum2[i];
    return res;
}
ll range_ask(ll l, ll r){//l到r的区间和 
    return ask(r) - ask(l - 1);
}
int main(){
	//先不考虑原数组的影响,直接区间修改修改sum1和sum2
    //最后要求[l,r]的区间和的话 
	//结果就是range_ask(l,r)+d[r]-d[l-1]
	// d[r]-d[l-1]考虑原数组的前缀和
	return 0;
} 
  • 二维树状数组

如果不是做题,谁会作死搞二维数组的。

在一维树状数组中,tree[x](树状数组中的那个“数组”)记录的是右端点为x、长度为lowbit(x)的区间的区间和。
那么在二维树状数组中,可以类似地定义tree[x][y]记录的是右下角为(x, y),高为lowbit(x), 宽为 lowbit(y)的区间的区间和。

这个还是比较好理解的。

单点修改 + 区间查询

#include<bits/stdc++.h>
#define il inline
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=3e3+5;
int sum[maxn][maxn],n;
il void add(int x,int y,int z){//将(x,y) +x
	int py=y;
	while(x<=n+3){
		y=py;
		while(y<=n+3)	sum[x][y]+=z,y+=y&-y;
		x+=x&-x;
	} 
}
il ll ask(int x,int y){//求左上(1,1)右下(x,y)的矩阵和
	ll res=0,py=y;
	while(x){
		y=py;
		while(y)	res+=sum[x][y],y-=y&-y;
		x-=x&-x;
	} 
	return res;
}
int main(){
	std::ios::sync_with_stdio(0);

	return 0;
}

区间修改 + 单点查询

 

#include<bits/stdc++.h>
#define il inline
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=3e3+5;
int sum[maxn][maxn],n;
il void add(int x,int y,int z){
	int py=y;
	while(x<=n+3){
		y=py;
		while(y<=n+3)	sum[x][y]+=z,y+=y&-y;
		x+=x&-x;
	}
}
il void r_add(int xa,int ya,int xb,int yb,int z){//给左上(xa,ya)右下(xb,yb)矩阵+z 
	add(xa,ya,z),add(xa,yb+1,-z),add(xb+1,ya,-z),add(xb+1,yb+z,z);
} 
il ll ask(int x,int y){//查询(x,y)这点的值
	ll res=0,py=y;
	while(x){
		y=py;
		while(y)	res+=sum[x][y],y-=y&-y;
		x-=x&-x;
	} 
	return res;
}
int main(){
	
	return 0;
} 

 

区间修改 + 区间查询

 

#include<bits/stdc++.h>
#define il inline
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=3e3+5;
ll t1[maxn][maxn],t2[maxn][maxn],t3[maxn][maxn],t4[maxn][maxn];
int n;
il void add(int x,int y,int z){
	int py=y;
	while(x<=n+3){
		y=py;
		while(y<=n+3){
			t1[x][y]+=z,t2[x][y]+=z*x,t3[x][y]+=z*y,t4[x][y]+=z*x*y;
			y+=y&-y;
		}
		x+=x&-x;
	}
}
il void r_add(int xa,int ya,int xb,int yb,int z){
	add(xa,ya,z),add(xa,yb+1,-z),add(xb+1,ya,-z),add(xb+1,yb+1,z);
}
il ll ask(int x,int y){
	ll res=0,py=y;
	while(x){
		y=py;
		while(y){
			res+=(x+1)*(y+1)*t1[x][y]-(y+1)*t2[x][y];
			res+=-(x+1)*t3[x][y]+t4[x][y];
			y-=y&-y;
		}
		x-=x&-x;
	}
	return res;
}
il ll r_ask(int xa,int ya,int xb,int yb){
	return ask(xb,yb)-ask(xb,ya-1)-ask(xa-1,yb)+ask(xa-1,ya-1);
}
int main(){
	
	
	return 0;
} 
  • 再补上三维树状数组

区间修改+单点查询 

原理和二维一样不过就是再多加了一维。

//区间修改+单点查询 
#include<bits/stdc++.h>
#define il inline
#define pb push_back
#define fi first
#define se second
#define ms(_data,v) memset(_data,v,sizeof(_data))
#define sc(n) scanf("%d",&n)
#define SC(n,m) scanf("%d %d",&n,&m)
#define SZ(a) int((a).size())
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define drep(i,a,b)	for(int i=a;i>=b;--i)
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const double PI=acos(-1.0);
const double eps=1e-9;
const int maxn=105;
int sum[maxn][maxn][maxn],n;
il void add(int x,int y,int z,int w) {
	for(int i=x; i<=n+2; i+=i&-i) {
		for(int j=y; j<=n+2; j+=j&-j) {
			for(int k=z; k<=n+2; k+=k&-k)
				sum[i][j][k]+=w;
		}
	}
}
il void r_add(int x1,int y1,int z1,int x2,int y2,int z2,int w) {
	add(x1,y1,z1,w);
	add(x1,y2+1,z1,-w);
	add(x2+1,y1,z1,-w);
	add(x2+1,y2+1,z1,w);

	add(x1,y1,z2+1,-w);
	add(x1,y2+1,z2+1,w);
	add(x2+1,y1,z2+1,w);
	add(x2+1,y2+1,z2+1,-w);
}
il int query(int x,int y,int z){
	int res=0;
	for(int i=x;i;i-=i&-i){
		for(int j=y;j;j-=j&-j){
			for(int k=z;k;k-=k&-k)
				res+=sum[i][j][k];
		}
	}
	return res;
} 
int main() {
	std::ios::sync_with_stdio(0);

	return 0;
}



其他的还没遇到//

最后膜巨佬!Orz

完结撒花!!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值