树状数组

原理:

如图:
在这里插入图片描述
黑色数组代表原来的数组(下面用A[i]代替),红色结构代表我们的树状数组(下面用C[i]代替),发现没有,每个位置只有一个方框,令每个位置存的就是子节点的值的和,则有

C[1] = A[1];
C[2] = A[1] + A[2];
C[3] = A[3];
C[4] = A[1] + A[2] + A[3] + A[4];
C[5] = A[5];
C[6] = A[5] + A[6];
C[7] = A[7];
C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];
可以发现,这颗树是有规律的

C[i] = A[i - 2k+1] + A[i - 2k+2] + … + A[i]; //k为i的二进制中从最低位到高位连续零的长度

例如i = 8(1000)时候,k = 3,可自行验证。

这个怎么实现求和呢,比如我们要找前7项和,那么应该是SUM = C[7] + C[6] + C[4];

而根据上面的式子,容易的出SUMi = C[i] + C[i-2k1] + C[(i - 2k1) - 2k2] + …;

其实树状数组就是一个二进制上面的应用。
现在问题就是,如何求2k
这里,很神奇,2k=2k = i&(-i)
证明如下:
在这里插入图片描述
而且这个有一个专门的称呼,叫做lowbit,即取2^k。
这样,单点修改和区间查询就变得简单了,而区间修改和单点查询,只需要引入一点查分的思想

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,a[1000005],c[1000005];
//求x二进制下连续0个数 
int lowbit(int x){
	return x&(-x);	
}
//更新a[x]使之增加k 
void update(int x,int k){
	while(x<=n){
		c[x]+=k;
		x+=lowbit(x);
	}
	return;
}
//求a[1]~a[x]之和 
int summary(int x){
	int ans=0;
	while(x>0){
		ans+=c[x];
		x-=lowbit(x);
	}
	return ans;
}
/*单点更新,区间求和 
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		update(i,a[i]);
	}
	for(int i=1;i<=m;i++){
		int b,x,k;
		scanf("%d%d%d",&b,&x,&k);
		if(b==1) update(x,k);
		else printf("%d\n",summary(k)-summary(x-1));
	}
	return 0;
}*/
//区间更新,单点求值 
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		update(i,a[i]-a[i-1]);//差分思想 
	}
	for(int i=1;i<=m;i++){
		int b,x,y,k;
		scanf("%d",&b);
		if(b==1){
			scanf("%d%d%d",&x,&y,&k);
			update(x,k);//更新a[i]-a[i-1]增加k 
			update(y+1,-k);//更新a[y+1]-a[y]减少k 
		}
		else{
			scanf("%d",&x);
			printf("%d\n",summary(x));
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值