树状数组

本文深入讲解树状数组的原理及应用,包括单点更新、区间查询、差分形式树状数组的实现,以及如何处理区间更新和区间查询的复杂场景。通过具体的代码示例,帮助读者理解树状数组在解决数据结构问题中的高效性和灵活性。
摘要由CSDN通过智能技术生成

查询和更新的复杂度均为 O(log n)。

单点更新,单点查询

传统数组直接可以搞定。

单点更新,区间查询

树状数组来完成。

#define MAX 100
int n;  //题目实际输入的树状数组的大小
int init[MAX], tree[MAX];  //初始数据存放数组,树状数组

int lowbit(int position){  //计算下一个position关联位置
	return position & (-position);
}

void update(int position, int add_num){  //更新tree[position]的值,使其加上add_num
	while(position <= n){  //position位置数据更新只会影响到position及其后数据
		tree[position] += add_num;
		position += lowbit(position);
	}
}

int sum(int position){  //求数组init[]中前position个数据之和
	int res = 0;
	while(position > 0){  //求前position个和,position需要减小
		res += tree[position];
		position -= lowbit(position);
	}
	return res;
}

/*---使用方法---*/
update(position, add_num);  //单点更新
res = sum(right) - sum(left - 1);  //查询区间[left, right]间数据和

区间更新,单点查询

此时直接对区间内数据都进行更新,将导致复杂度上升,因此引入差分形式的树状数组。举例:对于数据init[] = 1 3 4 6 8构建差分形式的树状数组diff_tree[] = 1 2 1 2 2,其中position位置的数据值为diff_tree[position] = init[position] - init[position - 1],其中约定init[0] = 0。当区间[2,4]间的数据同时加3时,此时差分形式的树状数组变为diff_tree[] = 1 5 1 2 -1,观察到只有2位置和5位置的数据进行了更新,因此将一个区间的更新变为仅更新两个单点,极大的减小了复杂度。而由于仅需单点查询,因此将树状数组中前position个和加起来即为第position个数据的值,即8 = init[5] = 1 + 5 + 1 + 2 + (-1) = diff_tree[1] + diff_tree[2] + ... + diff_tree[5]具体实现为:

#define MAX 100
int n;  //题目实际输入的树状数组的大小
int init[MAX], diff_tree[MAX];  //初始数据存放数组,树状数组

/*---三个关键函数和普通树状数组保持一致---*/
int lowbit(int position){
	return position & (-position);
}

void update(int position, int add_num){
	while(position <= n){
		diff_tree[position] += add_num;
		position += lowbit(position);
	}
}

int sum(int position){
	int res = 0;
	while(position > 0){
		res += diff_tree[position];
		position -= lowbit(position);
	}
	return res;
}

/*---使用方法---*/
update(left, add_num);  //区间最左侧的点加上add_num
update(right + 1, -add_num);  //区间右侧点的后一个值减去add_num

res = sum(position);  //查询position位置的值

区间更新,区间查询

由于存在区间更新,因此仍使用差分形式的树状数组。计算前position个数据和即为:
∑ i = 1 p o s i t i o n i n i t [ i ] = ∑ i = 1 p o s i t i o n ∑ j = 1 i d i f f _ t r e e [ j ] =   n × d i f f _ t r e e [ 1 ] + ( n − 1 ) × d i f f _ t r e e [ 2 ] + ⋯ + d i f f _ t r e e [ p o s i t i o n ] =   n × ( d i f _ t r e e [ 1 ] + d i f f _ t r e e [ 2 ] + ⋯ + d i f f _ t r e e [ p o s i t i o n ] ) − ( 0 × d i f f _ t r e e [ 1 ] + 1 × d i f f _ t r e e [ 2 ] + ⋯ + ( n − 1 ) × d i f f _ t r e e [ p o s i t i o n ] ) =   n × ∑ i = 1 p o s i t i o n d i f f _ t r e e [ i ] − ∑ i = 1 p o s i t i o n ( d i f f _ t r e e [ i ] × ( i − 1 ) ) \begin{aligned} \sum_{i = 1}^{position}{init[i]}=&\sum_{i = 1}^{position}{\sum_{j = 1}^{i}{diff\_tree[j]}} \\ =&\ n\times diff\_tree[1] + (n - 1)\times diff\_tree[2] +\dots +diff\_tree[position] \\ =&\ n\times(dif\_tree[1] + diff\_tree[2] +\dots+diff\_tree[position])\\ &-(0\times diff\_tree[1] + 1\times diff\_tree[2]+\dots+(n-1)\times diff\_tree[position]) \\ =&\ n\times \sum_{i = 1}^{position}{diff\_tree[i]}-\sum_{i=1}^{position}{(diff\_tree[i]\times (i-1))} \end{aligned} i=1positioninit[i]====i=1positionj=1idiff_tree[j] n×diff_tree[1]+(n1)×diff_tree[2]++diff_tree[position] n×(dif_tree[1]+diff_tree[2]++diff_tree[position])(0×diff_tree[1]+1×diff_tree[2]++(n1)×diff_tree[position]) n×i=1positiondiff_tree[i]i=1position(diff_tree[i]×(i1))
因此维护两个数组sum_tree[i] = diff_tree[i]sum_plus_tree[i] = diff_tree[i]*(i - 1)。具体实现如下:

#define MAX 100
int n;  //题目实际输入的树状数组的大小
int init[MAX];  //初始数据存放数组
int sum_tree[MAX];  //存储diff_tree[1] + diff_tree[2] + ... + diff_tree[n]
int sum_plus_tree[MAX];  //存储diff_tree[1]*0 + diff_tree[2]*1 + ... +diff_tree[n]*(n - 1)

/*---三个关键函数发生了更改---*/
int lowbit(int position){
	return position & (-position);
}

void update(int position, int add_num){
	int num = position;  //num值在更新其他关联位置时不变
	while(position <= n){
		sum_tree[position] += add_num;
		sum_plus_tree[position] += add_num * (num - 1);
		position += lowbit(position);
	}
}

int sum(int position){
	int res = 0, num = position;
	while(position > 0){
		res += num * sum_tree[position] - sum_plus_tree[position];
		position -= lowbit(position);
	}
	return res;
}

/*---使用方法---*/
update(left, add_num);  //区间最左侧的点加上add_num
update(right + 1, -add_num);  //区间右侧点的后一个值减去add_num

res = sum(right) - sum(left - 1);  //求区间[left, right]间数据和

前缀和

S i S_i Si记录前 i i i个数据和,求 ∑ i = 1 p o s S i \sum_{i=1}^{pos} S_i i=1posSi。此时公式变为
∑ i = 1 p o s S i = ( n + 1 ) × ∑ i = 1 p o s i t i o n t r e e [ i ] − ∑ i = 1 p o s i t i o n ( t r e e [ i ] × i ) \sum_{i=1}^{pos} S_i=(n+1)\times \sum_{i = 1}^{position}{tree[i]}-\sum_{i=1}^{position}{(tree[i]\times i)} i=1posSi=(n+1)×i=1positiontree[i]i=1position(tree[i]×i)

#include <iostream>
typedef long long ll;

const int nm_max = 1e5 + 5;
int n;
ll init[nm_max];
ll sum_tree[nm_max];  //存储sum_tree[n]=tree[1] + tree[2] + ... + tree[n]
ll sum_plus_tree[nm_max];  //存储sum_plus_tree[n]=tree[1]*1 + tree[2]*2 + ... +tree[n]*n

/*---三个关键函数发生了更改---*/
int lowbit(int position){
	return position & (-position);
}

void update(int position, ll add_num){
	int num = position;  //num值在更新其他关联位置时不变
	while(position <= n){
		sum_tree[position] += add_num;
		sum_plus_tree[position] += add_num * num;
		position += lowbit(position);
	}
}

ll sum(int position){
	ll res = 0;
    int num = position;
	while(position > 0){
		res += (num + 1) * sum_tree[position] - sum_plus_tree[position];
		position -= lowbit(position);
	}
	return res;
}

int main(){

    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i){
        scanf("%lld", &init[i]);
        update(i, init[i]);
    }

    for(int i = 1; i <= m; ++i){
        scanf("%s", ch);
        if(ch[0] == 'Q'){  //查询
            scanf("%lld", &x_num);
            printf("%lld\n", sum(x_num));
        }
        else if(ch[0] == 'M'){  //更新
            scanf("%lld%lld", &pos, &x_num);
            update(pos, x_num - init[pos]);
            init[pos] = x_num;
        }
    }

    return 0;
}

参考资料

树状数组详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

D-A-X

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值