树状数组

树状数组简介

树状数组维护信息的类型:

树状数组一般用来维护可差分的信息

比如:累加和、累加乘或者出题人出了某个可差分的信息

不可差分的信息:比如最大值、最小值

不可差分的信息一般不用树状数组来维护,而会选择线段树来维护,因为线段树维护的思考难度低

大多数情况下,线段树可以代替树状数组,两者的时间复杂度差不多,单次调用都是O(log N)

线段树的优势:用法全面、思考难度低、维护信息类型多(包括可差分信息、不可差分信息)

线段树的劣势:代码较多、空间使用较大、常数时间较差

树状数组的优势:代码少、使用空间少、常数时间优异

树状数组的劣势:维护信息的类型少、维护某些不可差分的信息时思考难度大并且不易实现

一维数组上实现:单点增加、范围查询的树状数组

洛谷测试链接

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define u unsigned
#define ll long long
#define sc scanf
#define pr printf 
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 100001

int tree[5 * N];//树状数组 
int n;//原始数组的元素个数 
int m;//操作次数 
//暂时存放数值的变量 
int t;
int a;
int b;

int lowbit(int x);//取出x的最右侧的一对应的数 
void add(int i, int v);//在i位置增加v 
int sum(int r);//返回[1, r]区间的累加和 
int range(int l, int r);//返回[l, r]区间的累加和 

int main(int argc, char* argv[])
{
	sc("%d%d", &n, &m);//读入元素个数和操作个数 
	
	for (int i = 1; i <= n; i ++) {//构成一个树状数组 
		sc("%d", &t);
		add(i, t);//假设树状数组刚开始都是0,现在开始添加数 
	}
	
	for (int i = 1; i <= m; i ++) {
		sc("%d%d%d", &t, &a, &b);//读入三个数 
		
		if (t == 1) {//如果t是1,把对应的位置的数加b 
			add(a, b);
		}
		else {//t是2,打印[a, b]区间的累加和	 
			pr("%d\n", range(a, b));
		}
	}
	
	return 0;
}

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

void add(int i, int v)
{
	while(i <= n) {//下标不能够超过元素个数 
		tree[i] += v;
		i += lowbit(i);//每次加lowbit去下一个包含i位置的区间 
	}
}

int sum(int r)
{
	int ans = 0;
	
	while(r) {
		ans += tree[r];
		r -= lowbit(r);//每次减lowbit去下一个包含[1, r]的区间 
	}
	
	return ans;
}

int range(int l, int r)
{
	return sum(r) - sum(l - 1);//前缀和求区间问题 
}

一维数组上实现:区间增加、单点查询的树状数组

洛谷测试链接

利用差分数组的性质来实现区间增加和单点查询,本质还是树状数组的操作但维护的信息不一样

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define u unsigned
#define ll long long
#define sc scanf
#define pr printf 
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 100001

//树状数组维护差分信息 
int tree[N * 5] = {0};
int n;//原始数组的元素个数 
int m;//操作的次数 
int pre;//前一个元素默认为0 
//暂时存放的树的变量 
int t; 
int a[3];

int lowbit(int x);//返回x最右边的1对应的十进制 
void add(int i, int v);//在树状数组i位置的值增加v 
int sum(int r);//返回[1, r]范围的累加和 

int main(int argc, char* argv[])
{
	sc("%d%d", &n, &m);//读取数组的元素个数和操作个数 
	
	//读入数组的值 
	for (int i = 1; i <= n; i ++) {
		sc("%d", &t);
		
		add(i, t - pre);//把差分数组放入树状数组,树状数组来维护差分信息 
		pre = t;//存储前面的元素值,不然无法得到差分数组 
	}
	
	for (int i = 1; i <= m; i ++) {
		sc("%d", &t);//读入操作的类型 
		
		if (t == 1) {//给区间中的所有树增加一个值 
			sc("%d%d%d", a, a + 1, a + 2);
			//差分的操作 
			add(a[0], a[2]);
			add(a[1] + 1, -a[2]);
		}
		else {//单点查询 
			sc("%d", &a[0]);
			pr("%d\n", sum(a[0]));//差分数组的性质 
		}
	}
	
	return 0;
}

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

void add(int i, int v)
{
	while(i <= n) {
		tree[i] += v;
		i += lowbit(i);
	}
}

int sum(int r)
{
	int ans = 0;
	
	while(r) {
		ans += tree[r];
		r -= lowbit(r);
	}
	
	return ans;
}

一维数组上实现:区间增加、区间查询的树状数组(无敌板子)

洛谷测试链接

差分数组求原数组的i位置的数就是求在差分数组中i位置的前缀和,那么对于原数组中第i位置的前缀和来说呢?   

 因此对于区间查询和区间增加我们需要维护两个信息,来保证我们的答案是对的。

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define u unsigned
#define ll long long
#define sc scanf
#define pr printf 
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 100001

ll tree1[N];//维护原始数组的差分信息 
ll tree2[N];//维护原始数组的(i - 1) * d[i]的差分数组的信息 
int n;//原始数组的元素个数 
int m;//操作的次数 
//临时存放的变量 
ll t;
ll a[3];

void add(ll tree[], int i, ll v);//在树状数组i位置添加值v 
ll sum(ll tree[], int r);//求树状数组[1, r]的累加和 
int lowbit(int x);//返回x最右侧的1对应的十进制 
void rangeadd(int l, int r, ll v);//让原数组中[l, r]的值都加v 
ll range(int l, int r);//返回原数组中[l, r]的累加和 

int main(int argc, char* argv[])
{
	sc("%d%d", &n, &m);//读入原数组中元素的个数和操作的次数 
	
	for (int i = 1; i <= n; i ++) {//读入原数组中的每个数 
		sc("%lld", &t);
		rangeadd(i, i, t);//元素数组中的第i位置相当于在[i, i]区间加一个t 
	}
	
	for (int i = 1; i <= m; i ++) {//读入m次操作 
		sc("%lld", &t);
		
		if (t == 1) {//代表在区间中增加一个数 
			sc("%lld%lld%lld", a, a + 1, a + 2);
			rangeadd(a[0], a[1], a[2]);
		}
		else {//代表输出区间的累加和 
			sc("%lld%lld", a, a + 1);
			pr("%lld\n", range(a[0], a[1]));
		}
	}
	
	
	return 0;
}

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

void add(ll tree[], int i, ll v)
{
	while(i <= n) {
		tree[i] += v;
		i += lowbit(i);
	}
}

ll sum(ll tree[], int r)
{
	ll ans = 0;
	
	while(r) {
		ans += tree[r];
		r -= lowbit(r);
	}
	
	return ans;
}

void rangeadd(int l, int r, ll v)
{
	//差分操作,在对原数组进行范围的增加 
	add(tree1, l, v);//在l位置加v 
	add(tree1, r + 1, -v);//在r + 1位置加-v 
	//在相同的位置进行操作,只是维护的值不一样,还是一样的树状数组 
	add(tree2, l, (l - 1) * v);//因为是(i - 1) * d[i]所以是(l - 1) * v; 
	add(tree2, r + 1, -(r * v));//因为是(i - 1) * d[i]所以是-(r * v)  
}

ll range(int l, int r)
{
	//差分数组中,求原数组的累加和的公式 
	return r * sum(tree1, r) - sum(tree2, r) - (l - 1) * sum(tree1, l - 1) + sum(tree2, l - 1);
}

二维数组上实现:单点增加、区间查询的树状数组

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define u unsigned
#define ll long long
#define sc scanf
#define pr printf 
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 10

int n;//矩阵的行 
int m;//矩阵的列 
int q;//操作的次数 
int tree[N];//维护原数组的信息 
//暂时存放的变量 
int t;
int a[4];

int lowbit(int x);//返回x最右侧的1对应的十进制 
void add(int i, int j, int v);//在原数组中i, j位置加个v 
int sum(int r, int b);//返回原数组中(1, 1)到(r, b)的累加和 
int range(int a, int b, int c, int d);//返回原数组中(a, b) -> (c, d)的累加和 

int main(int argc, char* argv[])
{
	sc("%d%d%d", &n, &m, &q);
	
	for (int i = 1; i <= n; i ++) {
		for (int j = 1; j <= m; j ++) {
			sc("%d", &t);
			add(i, j, t);
		}
	}
	
	for (int i = 1; i <= q; i ++) {
		sc("%d", &t);
		
		if (t == 1) {
			sc("%d%d%d", a, a + 1, a + 2);
			add(a[0], a[1], a[2]);
		}
		else {
			sc("%d%d%d%d", a, a + 1, a + 2, a + 3);
			pr("%d\n", range(a[0], a[1], a[2], a[3]));
		}
	}
	
	return 0;
}

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

void add(int i, int j, int v)
{
	for (i = 1; i <= n; i += lowbit(i)) {
		for (j = 1; j <= m; j += lowbit(j)) {
			tree[i][j] += v;
		}
	}
}

int sum(int r, int b)
{
	int ans = 0; 
	
	for (int i = r; i ; i -= lowbit(i)) {
		for (int j = b; j ; j -= lowbit(j)) {
			ans += tree[i][j];
		}
	}
	
	return ans;
} 

int range(int a, int b, int c, int d)
{
	//二维前缀和 
	return sum(c, d) - sum(c, b - 1) - sum(a - 1, d) + (a - 1, b - 1);
}

二维数组上实现:区间增加、区间查询的树状数组

洛谷测试链接

在二维差分数组中求左上角(1, 1)到(x, y)的位置的累加和有公式,即SUM(1, 1, x, y),d[i][j]表示原数组的差分数组

利用这个公式可以快速实现区间查询,而区间增加则是利用差分数组来实现

代码实现

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>
#define u unsigned
#define ll long long
#define sc scanf
#define pr printf 
#define fr(i, j, n) for (int i = j; i < n; i++)
#define N 2049

int n;//矩阵的长度 
int m;//举证的宽度 
int tree1[N][N];//差分数组 
int tree2[N][N];//差分数组 * 行数 
int tree3[N][N];//差分数组 * 列数 
int tree4[N][N];//差分数组 * 行数 * 列数 
//暂时存放数值的变量 
char s;
int a[5];

int lowbit(int x);//返回x最右的1对应的十进制 
void rangeadd(int a, int b, int c, int d, int v);//在原始数组中(a, b) -> (c, d)都加上v  
void add(int x, int y, int v);//在树状树状数组中加上v 
int range(int a, int b, int c, int d);//返回原数组中(a, b) -> (c, d)的累加和 
int sum(int x, int y);//返回原始数组中(1, 1) -> (x, y)的累加和 

int main(int argc, char* argv[])
{
	sc("%c%d%d", &s, &n, &m);
	
	getchar();
	
	while(~sc("%c", &s)) {
		if (s == 'L') {
			sc("%d%d%d%d%d", a, a + 1, a + 2, a + 3, a + 4);
			rangeadd(a[0], a[1], a[2], a[3], a[4]);
		}
		else {
			sc("%d%d%d%d", a, a + 1, a + 2, a + 3);
			pr("%d\n", range(a[0], a[1], a[2], a[3]));
		}
		getchar();
	}
	
	return 0;
}

int lowbit(int x)
{
	return x & -x;
}
//利用差分数组快速实现范围增加 
void rangeadd(int a, int b, int c, int d, int v)
{
	add(a, b, v);
	add(c + 1, b, -v);
	add(a, d + 1, -v);
	add(c + 1, d + 1, v);
}
//维护四个树状数组的信息 
void add(int x, int y, int v)
{
	int v2 = x * v;
	int v3 = y * v;
	int v4 = x * y * v;
	
	for (int i = x; i <= n; i += lowbit(i)) { 
		for (int j = y; j <= m; j += lowbit(j)) {
			//维护四个信息 
			tree1[i][j] += v;
			tree2[i][j] += v2;
			tree3[i][j] += v3;
			tree4[i][j] += v4;
		}
	}
}
//二维前缀和加容斥原理 
int range(int a, int b, int c, int d)
{
	return sum(c, d) - sum(c, b - 1) - sum(a - 1, d) + sum(a - 1, b - 1);
}

int sum(int x, int y)
{
	int ans = 0;
	
	for (int i = x; i; i -= lowbit(i)) {
		for (int j = y; j; j -= lowbit(j)) {
			ans += (x + 1) * (y + 1) * tree1[i][j] - (y + 1) * tree2[i][j] - (x + 1) * tree3[i][j] + tree4[i][j];//在差分数组中求(1, 1) -> (x, y)的累加和的公式 
		}
	}
	
	return ans;
}

 

posted @ 2024-03-14 23:13  lwj1239  阅读(38)  评论(0)    收藏  举报