E - 树状数组 1

E - 树状数组 1

原题链接

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 x

  • 求出某区间每一个数的和

输入格式

第一行包含两个正整数 n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:

  • 1 x k 含义:将第 x 个数加上 k

  • 2 x y 含义:输出区间 [x,y] 内每个数的和

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

样例 #1

样例输入 #1

5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4

样例输出 #1

14
16

提示

【数据范围】

对于 30% 的数据,1n81m10
对于 70% 的数据,1n,m104
对于 100% 的数据,1n,m5×105

数据保证对于任意时刻,a 的任意子区间(包括长度为 1n 的子区间)和均在 [231,231) 范围内。

样例说明:

故输出结果14、16

题意

已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 x

  • 求出某区间每一个数的和

树状数组解决的基本问题

在一个序列中
修改某一个数(logn)
快速求前缀和(logn)

思考:朴素算法如何实现?

直接使用数组存每一个数,那么我们求前缀和的复杂度就是O(n),修改一个数的复杂度就是O(1)
使用前缀和数组,那么我们求前缀和的复杂度就是O(1),修改一个数的复杂度就是O(n)

“鱼和熊掌不可兼得”,如果第一个操作很快,那么第二个操作就会变得很慢

假设我们有m次询问,要看最坏的情况,那么这两种方式复杂度都到达了O(nm)的级别
通过树状数组,我们可以把两种操作都做到logn级别,也就是O(mlogn),效率得到了很大的提升

lowbit函数(前置知识)

定义一个函数f=lowbit(x),这个函数的值是x的二进制表达式中只保留最低位1得到的十进制数

比如:

(6)10=(110)2

那么lowbit(6)就等于2,因为(110)2中最低位(就是从右往左数的第二位)对应的数是(10)2=(2)10

所以假设一个数的二进制最低位的1在从右往左数的第k位,那么它的lowbit值就是

2k1

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

原理
根据计算机补码的性质。
补码就是原码的反码加一
如:

(110)2=(6)10

反码:

(001)2

加一:

(010)2

可以发现变为反码后 x 与反码数字位每一位都不同, 当反码加1,反码会逢1一直进位直到遇到0,且这个0变成了1,所以这个数最后面构造了一个 100… 串。 只有一个1,因此进行&运算后除了该位上存在1,其他位都是0(反码的最低位0变成1,与反码取反前的原码相同都是1),进而得到二进制表达式中只保留最低位1得到的十进制数

树状数组的定义

树状数组是一种维护前缀和的数据结构,可以实现 O(logn)查询一个前缀的和,O(logn)对原数列的一个位置进行修改。

  • 与前缀和相同的是,树状数组使用与原数列大小相同的数组即可维护
  • 与前缀和不同的是,树状数组的一个位置 i 存储的是从 i 开始,(包括 i)向前ti个元素的和
    ti就是最大的可以整除 i2的幂(通过上述lowbit函数可以得到)

树状数组的性质

设树状数组为b
性质1:有上述定义得到b[k]=klowbit(k)+1k
性质2:父节点 b[dad]=b[k]+lowbit(k)
image

(上为树状数组b,下为原数组a)

树状数组单点修改

根据性质2可以改造出对树状数组单点修改的函数
add(int k,int x)功能:下标为k的增加x

void add(int k,int x){
	while(k <= n){	//不能超范围
		b[k] += x;
		k += lowbit(k);	//维护父节点
	}
}

注意:空数组本身就是一个树状数组(和均为0),因此原数组输入数据维护树状数组b时可以直接add(i,ai)

树状数组区间查询

作差求lr的区间和:sum[1r] - sum[1~l-1]
根据性质1

LL getsum(int l,int r){		
	l --;
	LL s1 = 0;
	while(l){
		s1 += b[l];
		l -= lowbit(l);
	}
	LL s2 = 0;
	while(r){
		s2 += b[r];
		r -= lowbit(r);
	}
	return s2 - s1;
} 

最终代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;

#define X first
#define Y second

typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 1e6+10;
const int M = 2e5+10;
int n,m;
int a,b[N];

int lowbit(int x){
	return x & -x;
}
void add(int k,int x){
	while(k <= n){
		b[k] += x;
		k += lowbit(k);
	}
}
LL getsum(int l,int r){
	l --;
	LL s1 = 0;
	while(l){
		s1 += b[l];
		l -= lowbit(l);
	}
	LL s2 = 0;
	while(r){
		s2 += b[r];
		r -= lowbit(r);
	}
	return s2 - s1;
} 

void solve(){
	cin >> n >> m;
	for(int i = 1; i <= n; i ++ ){
		cin >> a;
		add(i,a);
	}
	while(m -- ){
		int op;
		cin >> op;
		if(op == 1){
			int k,x;
			cin >> k >> x;
			add(k,x);
		}
		else{
			int l,r;
			cin >> l >> r;
			cout << getsum(l,r) << nl;
		}
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);

	solve();
}
posted @   Keith-  阅读(59)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示