学习记录 树状数组

如果需要对序列进行以下两种操作:

  1. 将某一个数加上 x
  2. 求出某区间每一个数的和

朴素方法肯定不行,使用分块可以降低复杂度,但仍然有 O ( n ) O(\sqrt n) O(n ),数据非常大的话就不能过。使用线段树是一种不错的方法,但浪费空间且常数大。

分析发现可以去掉线段树中不需要的结点。具体地说,去掉每一个结点的右儿子,这时线段树就被简化为这个样子:
在这里插入图片描述
这种数据结构叫树状数组(BIT),下面讨论如何解决上面的两个问题。

例如:要求[1,5]的和,我们可以求上图中4与5两块的和
同样,要求[1,7]的和,我们可以求上图中7、6、4三块的和
如果将编号转化为二进制,通过观察发现,要求[1,x]的和,只要重复执行下面三个步骤:
1.结果加上BIT[x]
2.取x的最低位1(即执行x&-x),假设得到的数为y
3.令x-=y
直到x=0即可
因此,只要求解两次并将结果相减就可以完成查询。

如果要更新,只要从x开始每次加上y,直到x>n即可。

代码如下:

#include <cstdio>
#include <algorithm>
#define long long long
const int M=(int)5e5;
using namespace std;
long bit[M*2];
int n;
inline int lowbit(int x) {return x&(-x);}
inline void update(int i,long x) {
	while(i<=n) {
		bit[i]+=x;
		i+=lowbit(i);
	}
}

inline long query(int i) {
	long res=0;
	while(i>0) {
		res+=bit[i];
		i-=lowbit(i);
	}
	return res;
}

int main() {
    int m;
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++) {
    	long x;
    	scanf("%lld",&x);
    	update(i,x);
	}
	//1为更新,2为查询
	while(m--) {
		short op;
		int l;
		long r;
		scanf("%hd%d%lld",&op,&l,&r);
		if(op==1)	update(l,r);
		else    printf("%lld\n",query(r)-query(l-1));
	}
    return 0;
}



下面讨论如何用树状数组高效完成下面的操作:
  1. 将某区间每一个数数加上 x;
  2. 求出某一个数的值。

直接用树状数组无法完成。但发现树状数组有明显的类似前缀和的特性。因此将数组转化为差分(此处指前缀和逆运算)数组,修改[l,r]内的值等价于修改差分数组l,r+1两点的值。由于此处是加法,因此差分数组中的第l项+1,第r+1项-1。

代码如下:

#include <cstdio>
#include <algorithm>
#define lowbit(x) (x)&(-(x))
#define long long long
const int M=(int)5e5;
using namespace std;
long bit[M*2];
int n;
inline void update(int i,long val) {
	while(i<=n) {
		bit[i]+=val;
		i+=lowbit(i);
	}
}

inline long query(int i) {
	long res=0;
	while(i>0) {
		res+=bit[i];
		i-=lowbit(i);
	}
	return res;
}

int main() {
    int m;
    scanf("%d%d",&n,&m);
    long x,last=0;
    for(int i=1; i<=n; i++) {
    	scanf("%lld",&x);
    	update(i,x-last);  //记住树状数组里存放的不是实际值
    	last=x;
	}
	while(m--) {
		char op;
		scanf("%hhd",&op);
		if(op==1) {
			int x,y,k;
			scanf("%d%d%d",&x,&y,&k);
			update(x,k);
			update(y+1,-k);
		}
		else {
			int x;
			scanf("%d",&x);
			printf("%lld\n",query(x));
		}
	}
    return 0;
}

附: 下面给出树状数组求逆序对的方法:
1.对给定数据a[]进行离散化。注意为了处理重复数据,必须用第一种方法离散化,且应进行稳定排序。
2.对每一个a[i],先执行将树状数组第a[i]个元素+1(update(a[i],1)),再将结果加上a[i]减去树状数组前a[i]项的和(query(a[i]))。

原理:求逆序对,可以看成对于每一个位置上的数,求在它前面且比它大的数总共有多少个。
将树状数组中的数看成每个数出现的次数,这样,对于任意一个数a[i],树状数组中前a[i]项的和就是之前已经更新过的(即位置在a[i]前面)且不比a[i]大的数字的个数,用a[i]在原数组中升序排列的序数减去它,即得到以逆序对个数。为了减少空间浪费,故进行离散化。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值