树状数组
树状数组简介
树状数组维护信息的类型:
树状数组一般用来维护可差分的信息
比如:累加和、累加乘或者出题人出了某个可差分的信息
不可差分的信息:比如最大值、最小值
不可差分的信息一般不用树状数组来维护,而会选择线段树来维护,因为线段树维护的思考难度低
大多数情况下,线段树可以代替树状数组,两者的时间复杂度差不多,单次调用都是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;
}