【练习-1】树状数组-模板一(单点修改,区间查询)

因为是树状数组的第一篇,所以可能会略微加一点树状数组的介绍(其实我也不清楚 )。
直接由题目引入,然后分析。
树状数组:
1.单点修改,区间查询
2.区间修改,单点查询
3.区间修改,区间查询

单点修改,区间查询

Description
给定数列a[1],a[2],…,a[n],你需要依次进行 q个操作,操作有两类:

1 i x:给定i,x,将a[i]加上x;

2 l r:给定l,r,求 ∑ri=la[i]的值(换言之,求a[l]+a[l+1]+⋯+a[r]的值)

Input
第一行包含2个正整数n,q,表示数列长度个数,保证a≤n,q≤106
第二行n个整数a[1],a[2],…,a[n],表示初始数列,保证|a[i]|≤106
接下来q行,每行一个操作,为下列两种之一:

1 i x:给定i,x,将a[i]加上x;

2 l r:给定l,r,求 ∑ri=1a[i]的值;

保证1≤l≤r≤n, |x|≤106
Output
对于每个 2 l r 操作输出一行,每行有一个整数,表示所求的结果。

Samples
Input
3 2
1 2 3
1 2 0
2 1 3
Output
6
Hint
对于所有数据,1≤n,q≤106,|a[i]|≤106,1≤l≤r≤n,|x|≤106

AC code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,q,x,w,t,tt;
ll c[1111111];
ll lowbit(ll x){return x&(-x);}
void add(ll x,ll w)
{
	while(x<=n)
	{
		c[x]+=w;
		x += lowbit(x);
	}
}
ll sum(ll x)
{
	ll s=0;
	while(x>0)
	{
		s+=c[x];
		x -= lowbit(x);
	}
	return s;
}
int main()
{
	cin>>n>>q;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&w);
		add(i,w);
	}
	while(q--)
	{
		scanf("%lld%lld%lld",&x,&t,&tt);
		if(x==1)
			add(t,tt);
		if(x==2)
			printf("%lld\n",sum(tt)-sum(t-1));
	}
	
	return 0;	
}

树状数组的基本要素

①lowbit函数:

它的作用是检验这个数组下内含着多少个数,如下图,可以得知只有偶数的数组才含有多个,但实际上与数量有关的是该数化为二进制后,后面0的数量
例如:4的二进制是100,那么他就含有4个数(后面有2个0)(100)(二进制–>十进制)。
6的二进制是110,那么他就含有2个数(前面只有一个0)(10)(二进制–>十进制)。
7的二进制是111,那么他就含有1个数(没有0)(1)(二进制–>十进制)。
……以此类推。

ll lowbit(ll x){return x&(-x);}

那么如何靠x&(-x)就能实现得到后面有多少个0的操作呢?
例如一个8位的6的二进制原码是:0000 0110,正数的原码=补码=0000 0110
我们也可以知道一个数负数的补码是原码取反+1。
所以 -6的补码 = 1111 1001 +1 = 1111 1010计算机内部运算靠补码
那么&如果对应位置都是1则是1
得到最后的补码是0000 0010也就是–>2
(其实很好想,在第一个1之前的数都会取反消掉,后面的0全变成1,第一个1变成0,+1以后,在第一个1的位置上出现1后面全部都是0) (1000–>(取反)0111–>(+1)1000)
然后返回这个值就得到了该数组内所含数的数量。
在这里插入图片描述

②add函数(根据题目要求):

void add(ll x,ll w)
{
	while(x<=n)
	{
		c[x]+=w;
		x += lowbit(x);
	}
}

两个位置,x表示要修改的值,w表示要增加的数量。
比如要x=3的时候, c[3]的值会修改,然后会加上c[3]含有数的数量到达c[4]再加上c[4]含有数的数量到达c[8]结束,这样操作,把所有含有这个数(c[3])的数组全部都加上了w。

③sum函数:

ll sum(ll x)
{
	ll s=0;
	while(x>0)
	{
		s+=c[x];
		x -= lowbit(x);
	}
	return s;
}

由于树状数组的特性,无法直接得到数(为了压缩,比如上图的a[4]不能直接得到,需要c[4]-c[3]-c[2])。
所以由这个函数得到的数其实是前缀和。例如c[4]=a[1]+a[2]+a[3]+a[4]。

④main函数:

int main()
{
	cin>>n>>q;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&w);
		add(i,w);
	}
	while(q--)
	{
		scanf("%lld%lld%lld",&x,&t,&tt);
		if(x==1)
			add(t,tt);
		if(x==2)
			printf("%lld\n",sum(tt)-sum(t-1));
	}
	
	return 0;	
}

通过上面的解释,代码就很清楚了,没有什么难懂的地方了,因为是前缀和所以求区间值得时候按照前缀和的公式去求即可(求3-6的和就是 sum(6)-sum(2))。

以上只是鄙人的拙见,如果有错误、不足之处,还请指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值