树状数组题型解析(强推)

关于树状数组的深入理解学习建议看这位大佬的文章:
树状数组简单易懂
我这里直接给出最经典的图:
在这里插入图片描述
lowbit函数用于取到最低的1的位置,不做多余阐述。
定义两个数组,一个是题目要求的数组,还有一个就是f数组,f数组的作用:
f[i]就表示第i个点往前数lowbit(i)个点的区间和。

题型一:单点修改,区间查询

题目描述
如题,已知一个数列,你需要进行下面两种操作:
将某一个数加上 x
求出某区间每一个数的和
输入格式
第一行包含两个正整数 n,m分别表示该数列数字的个数和操作的总个数。
第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:
1 x k 含义:将第 x 个数加上 k
2 x y 含义:输出区间 [x,y]内每个数的和
输入样例
5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
输出样例
14
16
分析

首先我们按照上面所说的要求处理f数组的数据,即f[i]为a[i]及其前lowbit(i)的区间和,那么显而易见,我们要求a[1-x]的区间和,先加上f[x]得到[x-lowbit(x)+1,x]这一段的区间和,然后将x减去lower(x),逐次下去就能得到a[1-x]的区间和的值了(要注意f数组存储的每一个值都是后缀的区间和,由后缀求前缀就需要这么逆序了。)
比如我们要求1到6的区间和,我们在f数组从下标为6开始遍历,f[6]=a[5]+a[6],f[4]=a[1]+a[2]+a[3]+a[4],然后下标减为0,那么我们要求的值ans=f[6]+f[4],完成这一步骤,最关键的是这个lowbit函数和逆序遍历f数组这个操作,看着就比暴力法省时多了。(一切都对照上面的“神图”)
那么我们要求某个子区间的区间和也很容易解决了,比如我们要求区间在[l,r]的区间和,我们就可以利用前面所说的后缀转前缀的方法,分别得到前r项的和以及前l-1项的和,然后两式相减自然就能得到答案了。

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&-x)
int n,m,a[500005],f[500005];
void add(int x,int num){
	for(;x<=n;x+=lowbit(x))f[x]+=num;
}
int query(int x){
	int ans=0;
	for(;x>0;x-=lowbit(x))ans+=f[x];
	return ans;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;++i)scanf("%d",&a[i]),add(i,a[i]);
	while(m--){
		int t,x,y;
		scanf("%d%d%d",&t,&x,&y);
		if(t==1)add(x,y);
		else printf("%d\n",query(y)-query(x-1));
	}
}

题型二:区间修改,单点查询

题目描述
如题,已知一个数列,你需要进行下面两种操作:
将某区间每一个数加上x;
求出某一个数的值。
输入格式
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含2或4个整数,表示一个操作,具体如下:
操作 11: 格式:1 x y k 含义:将区间 [x,y][x,y] 内每个数加上k;
操作 22: 格式:2 x 含义:输出第x个数的值。
输出格式
输出包含若干行整数,即为所有操作2的结果。
输入样例
5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4
输出样例
6
10
分析

这题是利用了差分的思想,为什么要使用差分呢,我可以用数学式子推出。
为什么使用拆分呢?
对于一个数组a,另外再开一个数组f,f[i]用来存储a[i]-a[i-1]的值,那么就可以发现当我们修改某个区间的值的时候,假设我们修改的区间为[l,r],那么在[l+1,r]的部分,f数组的值是不会变的,因为a数组在这一范围内变化了同样的数组,会变的点只有两个,由于a[l]变化了但是a[l-1]并没有发生变化,那么f[l]变化了和a[l]一样的值,同理,a[r+1]没有发生变化,但是a[r]却发生了变化,很显然,f[r+1]的变化是a[r]变化的相反数。
现在来讲讲如何实现单点值的求解,这也就是差分法的精髓(曾经全国数学比赛的杯赛阶段思考过一些有关这方面的精髓,这里也算很好的阐释了):
f[i]=a[i]-a[i-1]
f[i-1]=a[i-1]-a[i-2]

f[2]=a[2]-a[1]
f[1]=a[1]-a[0]
这i个式子全部加起来,就可以得到f[i]+f[i-1]+…+f[1]=a[i],正好,这不刚好就是下标为i的元素单点值吗,所以,这实际上是一种数学的思维变化,我们知道这些以后可以把这些东西当成板子,但是思维的建立还是最重要的。

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&-x)
int n,m,f[500005];
void add(int x,int num){
	for(;x<=n;x+=lowbit(x))f[x]+=num;
}
int query(int x){
	int ans=0;
	for(;x>0;x-=lowbit(x))ans+=f[x];
	return ans;
}
int main(){
	int pre=0,last;
	cin>>n>>m;
	for(int i=1;i<=n;++i)scanf("%d",&last),add(i,last-pre),pre=last;
	while(m--){
		int t,x,y,k;
		scanf("%d%d",&t,&x);
		if(t==1){
			scanf("%d%d",&y,&k);
			add(x,k);
			add(y+1,-k);
		}
		else printf("%d\n",query(x));
	}
}
  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布布要成为最负责的男人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值