【洛谷】P3368 【模板】树状数组 2 (单点修改+区间查询或区间修改+单点查询的作用)

(家人这是我在其他地方借鉴的,真的真的感觉很好就记录分享给大家啦~,也顺便当成个笔记方便回顾(逃~))

树状数组是用数据压缩的思想由二进制实现的数据结构。有单点修改+区间查询或区间修改+单点查询的作用。

实现单点修改&区间查询

首先我们来看看暴力的效率。q组询问,极端情况下n个数的修改,效率为 O(n q) 。n,q为500000时一定会炸。

切入点:lowbit函数

由于电脑一种叫做补码的操作(由于电脑是二进制,它们存的相反数是它的取反+1),一个数与它的相反数做与操作时会返回二进制下最右边的1的位置。举个例子: 6&-6=2 将6变成二进制:110。其中最右侧的1加粗字体:110则返回的是二进制下10的值:2。得到代码:

//ll就是long long
ll lowbit(ll num){
	return num&-num;
}

利用这个性质建立树状数组:

为了简化区间修改的效率,我们需要建立这样的一个数组:数组中第k位的值为原数组中的一段区间和,这个区间的长度是lowbit(k),终点是k。 比如:输入一个数组,那么我们所建立的数组:

  • 第一位(1在二进制下=1 二进制下的1=1)的值为输入的数组的第一位往前的一位的和,也就是第一位。
  • 第二位(2在二进制下=10 二进制下的10=2)的值为输入的数组的第二位往前两位的和,第一位和第二位。
  • 第三位(3在二进制下=11 二进制下的1=1)的值为输入的数组的第三位往前一位的和,也就是第三位。
  • 第四位的值(4在二进制下=100 二进制下的100=4)的值为输入的数组的第四位往前四位的和,也就是第一位,第二位,第三位以及第四位。

我们就称这种数组为树状数组

代码

void build(ll s,ll num){
	for(ll i=s;i<=n;i+=lowbit(i)) tree[i]+=num;//当s在i的范围内 第num位数组加上num 
}

跟着代码走一遍:

  • 假设输入的n=5,输入的数为1 5 4 2 3(样例)
  • 输入第一个数时s=1,num=1。加上的树状数组数组位数为第一位,第二位,第四位。树状数组为:1 1 0 1 0
  • 输入第二个数时s=2,num=5,加上的位置为第二位,第四位。数组为1 6 0 6 0
  • 输入第三个数时s=3,num=4,加上的位置为第三位。数组为1 6 4 10 0
  • 输入第四个数时s=4,num=2,加上的位置为第四位。数组为1 6 4 12 0
  • 输入第五个数时s=5,num=3,加上的位置为第五位。数组为1 6 4 12 3

至此,建树操作已经完成了。可以发现这个操作的本质就是修改树状数组的值,所以它也是单点修改的函数

查询也是一样的。注意我们的操作不是区间求和,而是求两个前缀和的差!

//反着的建树。此操作是求输入的数据第1到第s位的和。
ll ask(ll s){
	ll ans=0;
	for(ll i=s;i>=1;i-=lowbit(i)) ans+=tree[i];//建树的反操作 
	return ans;
}

举一个sample(example) 求第三项和第五项之间的和。 本质上就是求5的前缀和与2的前缀和的差。(1 2 3 4 5)-(1 2)=(3 4 5)

  • 先求5的前缀和。所求的就是第5(101)项+第4(100)项就是12+3=15。
  • 再求2的前缀和。所求的就是第2(10)项就是6。
  • 最后作差就是15-6=9。

检验一下,3到5位的求和就是4+2+3=9。没有问题!

把所有的结合起来就是树状数组啦~

代码

ACcode:


#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+10;
int n,m,q,in[N],tr[N];
int lowbit(int num) {
	return num&-num;//返回值为二进制下num从左往右第一个1的位置 
}
void add(int s,int num) {
	for(int i=s; i<=n; i+=lowbit(i))tr[i]+=num;
	//当s在i的范围内,第num位数组加上num 
}
int ask(int s) {
	int ans=0;
	for(int i=s; i>=1; i-=lowbit(i))ans+=tr[i];//寻找差分的标记 
	return ans;
}
void solve() {
	cin>>n>>q;
	for(int i=1; i<=n; i++) cin>>in[i];
	while(q--) {
		int op;
		cin>>op;
		if(op==1) {
			int x,y,s;
			cin>>x>>y>>s;
			add(x,s);//差分 
			add(y+1,-s);
		} else {
			int x;
			cin>>x;
			cout<<in[x]+ask(x)<<"\n";//区域查询则为右边界前缀和减去左边界前缀和 
		}
	}
}
signed main() {
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int t=1;
//	cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}




over~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值