文章目录
树状数组
概念:
树状数组是一种可以快速区间修改和元素查询的结构,是线段树的低配版。原理是利用lowbit分解,大致感觉和二进制分解比较像,时间同样是l o g loglog级别的 。其实英文和它的作用更加贴切,就是通过二进制的索引产生像树一样的结构。
算法解释
树状数组的本质是用一个 "压缩版" 的树结构来存储数组的前缀和,核心是**lowbit **
关于lowbit
lowbit (x) 的定义:返回 x 的二进制表示中,最低位的 1 所对应的值。
比如:
x=5(二进制 101),最低位 1 在第 0 位(从 0 开始计数),对应值为 1,故 lowbit (5)=1;
x=6(二进制 110),最低位 1 在第 1 位,对应值为 2,故 lowbit (6)=2;
x=8(二进制 1000),最低位 1 在第 3 位,对应值为 8,故 lowbit (8)=8。
计算方式:负数在计算机是补码,x & -x就能提取最低位 1,是整个树状数组的根基。。
基础模板:单点修改,区间求和
规则
操作1:单点增加
操作2:求l,r区间和
核心真相:tree[]数组≠原数组a[]!!!, tree[x存的是一段区间的和,不是ax。奇数位置是原数组的值,偶数位置是区间和
举例子 n=5
a[1]=1 a[2]=5 a[3]=4 a[4]=2 a[5]=3
画图解释

//这个操作得到tree
for(int i = 1; i <= n; i++)
{
int val;
cin >> val;
update(i, val);
}
单点增加
//单点增加:i位置加上delta
void update(int i, int delta)
{
while(i <= n)
{
tree[i] += delta;
i += lowbit(i);
}
}
初始化a1\~n,得到偶数下标的数treei是区间和,奇数下标treei=ai
update(1,1); //a[1]=1
update(2,5); //a[2]=5
update(3,4); //a[3]=4
update(4,2); //a[4]=2
update(5,3); //a[5]=3
tree[1]=1
tree[2]=6 //含义 a1+a2 =1+5=6
tree[3]=4 //含义 a3 =4
tree[4]=12 //含义 a1+a2+a3+a4 =1+5+4+2=12
tree[5]=3 //含义 a5 =3
查询前缀和
//查询前缀 [1,i] 的和
int query(int i)
{
int res = 0;
while(i > 0)
{
res += tree[i];
i -= lowbit(i);
}
return res;
}
query不是读取tree1,tree2,tree3直接当元素,而是挑选若干块tree区间块相加拼出前缀和
query(3)求1~3总和:
i=3,res += tree[3] → res=4; i=3‑lowbit(3)=2
i=2,res += tree[2] → res=4+6=10; i=2‑lowbit(2)=0停止
query(3)=tree[3]+tree[2];
query(7)=tree[7]+tree[6]+tree[4];
代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 500010;
int n, m;
int tree[N];
int lowbit(int x)
{
return x & (-x);
}
//单点增加:i位置加上delta,对应截图update
void update(int i, int delta)
{
while(i <= n)
{
tree[i] += delta;
i += lowbit(i);
}
}
//查询前缀 [1,i] 的和
int query(int i)
{
int res = 0;
while(i > 0)
{
res += tree[i];
i -= lowbit(i);
}
return res;
}
//求区间[l,r]和
int get_range(int l, int r)
{
return query(r) - query(l-1);
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
int val;
cin >> val;
update(i, val);
}
while(m--)
{
int op, x, y;
cin >> op >> x >> y;
if(op == 1)
{
//1 x k:x位置加上y
update(x, y);
}
else
{
//2 x y 查询[x,y]
cout << get_range(x, y) << '\n';
}
}
return 0;
}
进阶1模板:区间加,单点查询
规则
操作1:1 x y k:区间 ( x , y ) (x,y) (x,y) 每个数加上 k k k
操作2:2 x:输出第 x x x 个元素的值
画图分析


差分思想
原数组 a a a,差分数组 d d d:
-
区间 ( l , r ) (l,r) (l,r)加 k k k:
d[l]+=k; d[r+1]-=k -
a x = ∑ i = 1 x d i ax = \sum_{i=1}^x di ax=∑i=1xdi,也就是差分数组的前缀和就是原数组ax
单点增加
区间加等价:update(l,k); update(r+1,-k)
if(op == 1)
{
//操作1:1 x y k,区间[x,y]加上k
int x,y,k;
cin >> x >> y >> k;
update(x, k);
update(y+1, -k);
}
查询前缀和
查询单点ax等价:query(x)(求d的前缀和
else
{
//操作2:2 x,输出a[x] = query(x),差分数组前缀和
int x;
cin >> x;
cout << query(x) << '\n';
}
注意:初始化: d 1 = a 1 , d i = a i − a i − 1 d1=a1,di=ai-ai-1 d1=a1,di=ai−ai−1,把差分数组丢进树状数组。
代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 500010;
int n, m;
int tree[N];
int a[N]; //保存原始输入数组
int lowbit(int x)
{
return x & (-x);
}
//单点增加:i位置加上delta
void update(int i, int delta)
{
while(i <= n)
{
tree[i] += delta;
i += lowbit(i);
}
}
//查询前缀 [1,i] 的和
int query(int i)
{
int res = 0;
while(i > 0)
{
res += tree[i];
i -= lowbit(i);
}
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
}
//初始化差分数组 d,树状数组维护d
update(1, a[1]);
for(int i = 2; i <= n; i++)
{
update(i, a[i] - a[i-1]);
}
while(m--)
{
int op;
cin >> op;
if(op == 1)
{
//操作1:1 x y k,区间\([x,y]\)加上k
int x,y,k;
cin >> x >> y >> k;
update(x, k);
update(y+1, -k);
}
else
{
//操作2:2 x,输出a[x] = query(x),差分数组前缀和
int x;
cin >> x;
cout << query(x) << '\n';
}
}
return 0;
}