树状数组
用途:
① 、 修 某 个 点 的 值 — — 单 点 修 改 ①、修某个点的值——单点修改 ①、修某个点的值——单点修改
② 、 求 某 一 区 间 的 前 缀 和 — — 区 间 求 和 ②、求某一区间的前缀和——区间求和 ②、求某一区间的前缀和——区间求和
时 间 复 杂 度 O ( l o g 2 n ) 。 时间复杂度O(log_2n)。 时间复杂度O(log2n)。
示例
顾 名 思 义 : 以 树 的 形 状 存 储 。 顾名思义:以树的形状存储。 顾名思义:以树的形状存储。
以 16 个 数 的 区 间 为 例 ( 数 组 长 度 为 17 , 下 标 从 1 开 始 ) : 以16个数的区间为例(数组长度为17,下标从1开始): 以16个数的区间为例(数组长度为17,下标从1开始):
①
、
单
点
修
改
:
①、单点修改:
①、单点修改:
定 义 原 数 组 A , 树 状 数 组 t r , 初 始 化 为 0 , 输 入 的 过 程 实 际 上 是 一 个 单 点 修 改 操 作 。 定义原数组A,树状数组tr,初始化为0,输入的过程实际上是一个单点修改操作。 定义原数组A,树状数组tr,初始化为0,输入的过程实际上是一个单点修改操作。
每 输 入 一 个 A [ i ] , 会 影 响 树 上 t r 下 标 为 i + l o w b i t ( i ) 的 值 , 因 此 要 更 新 t r [ i + l o w b i t ( i ) ] , 直 到 i + l o w b i t ( i ) > 元 素 个 数 n 。 每输入一个A[i],会影响树上tr下标为i+lowbit(i)的值,因此要更新tr[i+lowbit(i)],直到i+lowbit(i)>元素个数n。 每输入一个A[i],会影响树上tr下标为i+lowbit(i)的值,因此要更新tr[i+lowbit(i)],直到i+lowbit(i)>元素个数n。
其 中 l o w b i t ( x ) 指 x 二 进 制 形 式 下 最 低 位 的 1 代 表 的 二 进 制 数 的 值 。 其中lowbit(x)指x二进制形式下最低位的1代表的二进制数的值。 其中lowbit(x)指x二进制形式下最低位的1代表的二进制数的值。
例 如 上 图 中 , 在 输 入 A [ 6 ] 的 值 后 , 受 影 响 的 点 是 8 和 16 , 故 要 给 节 点 8 和 16 加 上 A [ 6 ] 。 l o w b i t ( 6 ) = l o w b i t ( 110 ) = 10 = 2 , t r [ 6 + 2 ] = t r [ 8 ] + = A [ 6 ] , t r [ 8 + 8 ] = t r [ 16 ] + = A [ 6 ] 。 例如上图中,在输入A[6]的值后,受影响的点是8和16,故要给节点8和16加上A[6]。\\lowbit(6)=lowbit(110)=10=2,tr[6+2]=tr[8]+=A[6],tr[8+8]=tr[16]+=A[6]。 例如上图中,在输入A[6]的值后,受影响的点是8和16,故要给节点8和16加上A[6]。lowbit(6)=lowbit(110)=10=2,tr[6+2]=tr[8]+=A[6],tr[8+8]=tr[16]+=A[6]。
显 然 地 , 修 改 的 时 间 复 杂 度 为 O ( l o g 2 n ) 显然地,修改的时间复杂度为O(log_2n) 显然地,修改的时间复杂度为O(log2n)。
② 、 区 间 求 和 ②、区间求和 ②、区间求和
树 上 节 点 t r [ i ] = A [ i ] + ∑ A [ i − l o w b i t ( i ) ] , i + l o w b i t ( i ) > 0 , 树上节点tr[i]=A[i]+ \sum A[i-lowbit(i)], i+lowbit(i)>0, 树上节点tr[i]=A[i]+∑A[i−lowbit(i)],i+lowbit(i)>0,
求 区 间 [ l , r ] 的 和 : t r [ r ] − t r [ l − 1 ] 。 求区间[l,r]的和:tr[r]-tr[l-1]。 求区间[l,r]的和:tr[r]−tr[l−1]。
求 t r [ i ] 的 操 作 是 O ( l o g 2 n ) 的 , 因 此 区 间 求 和 的 时 间 复 杂 度 也 是 O ( l o g 2 n ) 的 。 求tr[i]的操作是O(log_2n)的,因此区间求和的时间复杂度也是O(log_2n)的。 求tr[i]的操作是O(log2n)的,因此区间求和的时间复杂度也是O(log2n)的。
代码:
int lowbit(int x)
{
return x&(-x);
}
void add(int w,int p)//单点修改
{
for(int i=p;i<=n;i+=lowbit(i)) tr[i]+=w;
}
int query(int x)//区间[1,x]的和
{
int sum=0;
for(int i=x;i;i-=lowbit(i)) sum+=tr[i];
return sum;
}
例题:
题目:
给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b] 的连续和。
输入格式
第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。
第二行包含 n 个整数,表示完整数列。
接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;k=1,表示第 a 个数加 b)。
数列从 1 开始计数。
输出格式
输出若干行数字,表示 k=0 时,对应的子数列 [a,b] 的连续和。
数据范围
1≤n≤100000,
1≤m≤100000,
1≤a≤b≤n
输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8
输出样例:
11
30
35
题意:
输 入 数 组 长 度 n , 操 作 数 m , 长 度 为 n 的 数 组 , m 个 操 作 。 k = 0 时 输 出 区 间 [ a , b ] 的 和 , k = 1 时 把 节 点 a 的 值 加 上 b 。 输入数组长度n,操作数m,长度为n的数组,m个操作。\\k=0时输出区间[a,b]的和,k=1时把节点a的值加上b。 输入数组长度n,操作数m,长度为n的数组,m个操作。k=0时输出区间[a,b]的和,k=1时把节点a的值加上b。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define inf 0x7fffffff
using namespace std;
const int N=1e5+10;
int n,m;
int tr[N],a[N];
int lowbit(int x)
{
return x&(-x);
}
void add(int w,int p)
{
for(int i=p;i<=n;i+=lowbit(i)) tr[i]+=w;
}
int query(int x)
{
int sum=0;
for(int i=x;i;i-=lowbit(i)) sum+=tr[i];
return sum;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
add(a[i],i);///建树,更新树上节点的值
}
while(m--)
{
int k,a,b;
scanf("%d%d%d",&k,&a,&b);
if(!k) printf("%d\n",query(b)-query(a-1));
else add(b,a);///在位置a加上b,修改
}
return 0;
}