树状数组概念与练习

本文详细介绍了树状数组的数据结构,包括其支持单点修改和区间查询的特点,管辖区域的计算方法,以及两种常见的树状数组建立方式。还涉及了区间修改与查询的技巧,如差分思想的应用。最后,提及了与洛谷竞赛相关的两道题目实例和一个精彩视频资源。
摘要由CSDN通过智能技术生成

1,作用

树状数组支持单点修改和区间查询,且其代码量最小。

2,样式

由树状数组构建的新数组长度和原数组长度是一样的。

3,管辖区域

如图所示,c[1]管辖了a[1],c[2]管辖了a[1]+a[2],每一个c[n]都有其特定的管辖区域。

而这个管辖区域其实就是n转化为2进制后,从后往前看第一个1所代表的数。

eg:6转化为二进制是110,管辖区域就是1*2^1=2,如图,c[6]管辖了两个数字,a[5],a[6]。

当然,求管辖区域有特定的代码:

inline int lowbit(int x) {
	return x & (-x);
}//&是一个位运算操作

4,建立树状数组

题目一般只给原始数组a[],要自己建立树状数组c[]

这时候有两种建树方式:

第一种是全部输入以后逆思维建树:

//建立树状数组
void build(int n) {
	int k = 1;   //从c[1]开始算
	while (k <= n) {
		for (int j = k; j > k - lowbit(k); j--) { //注意大于号
			c[k] += a[j];                        //先算出管辖区域,把管辖区域内所有a[i]相加
		}
		k++;
	}
	
}

第二种是每输入一个数a[i],就把他的所有父亲节点都加上这个数:

//建立树状数组
void build(int i,int num) {
	
	for (int j = i; j <= n; j += lowbit(j)) {
		c[j] += num;
	}
}

5,区间查询与单点修改

由上图可知,节点编号+区间长度=父亲编号eg:c[4]节点的父亲是c[8],而4+lowbit(4)=8;,因此,我们可以通过x+lowbit(x)的方式求得父亲编号;

a,  单点修改

假如要将一个点的数值加上x,那么这个点的所有父亲节点都要加上x,这样才是维护了这个树状数组。

//p为节点,加上x
void add(int p, int x) { 
	while (p < N) {   
		c[p] += x;
		p += lowbit(p);
	}
}

b,区间查询

假如要求一段区间之和,例如,要求a[6]到a[8]之和,我们可以先求a[1]到a[8]之和,再减去a[1]到a[5]之和,就得到了结果;

下面是求a[1]到a[p]之和的代码

//区间之和
//从1到p之和
ll count(int p) {
	ll result = 0;
	while (p > 0) {
		result += c[p];
		p -= lowbit(p);
	}
	return result;
}

6,区间修改与单点查询

a,差分

区间修改想要不TLE,必须学会差分的思想:

数组a[]={1,3,4,6,7,10},那么差分数组(b[i]=a[i]-a[i-1]   //a[0]=0)b[]={1,2,1,2,1,3}

如果a在2~4区间都加2,那么a[]={1,5,6,8,9,10},b[]={1,4,1,2,1,1}

所以,b数组的改变只是2处加2,4+1处减2,在2~4时因为前后同增同减,所以并不发生变化。

b,区间修改

7,模板题

a,洛谷p3374   

对应5,单点修改与区间查询

【模板】树状数组 1 - 洛谷

AC代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=5e5+10;
int c[N];
int a[N];

//管辖区间
//对于节点x
inline int lowbit(int x) {
	return x & (-x);
}
//建立树状数组
void build(int n) {
	int k = 1;
	while (k <= n) {
		for (int j = k; j > k - lowbit(k); j--) {
			c[k] += a[j];
		}
		k++;
	}
	
}

//单点修改
//p节点,加上x
void add(int p, int x) {
	while (p < N) {
		c[p] += x;
		p += lowbit(p);
	}
}
//区间之和
//从1到p之和
ll count(int p) {
	ll result = 0;
	while (p > 0) {
		result += c[p];
		p -= lowbit(p);
	}
	return result;
}
int main() {
	int n, m;
	int p, x, y;
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		
	}
	build(n);
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %d", &p, &x, &y);
		if (p == 1) {
			add(x, y);
		}
		else {
			printf("%lld\n", count(y) - count(x-1));
		}
	}
	return 0;
}

b,洛谷p3368

【模板】树状数组 2 - 洛谷

AC代码如下

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=5e5+10;
int c[N] = {0};
int a[N];
int n;

//管辖区间
//对于节点x
inline int lowbit(int x) {
	return x & (-x);
}

//差分修改
//其余都是零,p及其父亲节点叠加一个+的操作
void add(int p, int x) {
	while (p <=n) {
		c[p] += x;
		p += lowbit(p);
	}
}
//区间之和
//从1到p之和
ll count(int p) {
	ll result = 0;
	while (p > 0) {
		result += c[p];
		p -= lowbit(p);
	}
	return result;
}
int main() {
	int m;
	int p, x, y,z;
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		}
	for (int i = 1; i <= m; i++) {
		scanf("%d", &p);
		if (p == 1) {
			scanf("%d %d %d", &x, &y, &z);
			add(x, z);//大部分是0,区间左端叠加加法
			add(y + 1, -z);//区间右端叠加减法(而且求count时只会算一次)
		}
		if (p == 2) {
			scanf("%d", &x);
			printf("%d\n", a[x] + count(x));
		}
	}
	return 0;
}

8,非常精彩的视频

五分钟丝滑动画讲解 | 树状数组_哔哩哔哩_bilibili

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值