【算法】树状数组
1. 算法简介#
先来看一个很现实的问题:
就拿 [luogu]P3372【模板】线段树 1 这道题为例。
按常规做法,应该是用普通线段树 +
而如果用树状数组去做,只用
以下是数据对比:
很明显,在两者都开了
Q: 树状数组这么好用,为什么不直接用树状数组完全替代线段树呢?
这就又要讲到树状数组的特性:它是一颗‘前缀树’。也就是树状数组只能用于维护前缀信息,如前缀和、前缀积、前缀异或等等。想要维护区间信息,就必须要使维护的区间信息有 ‘可减性’(这里和线段树是相反的,线段树需要满足 ‘可合并性’)。在一些区间信息无 ‘可减性’ 的时候,树状数组就无法胜任。例如区间最值、区间第
Q: 学习树状数组需要哪些前置知识呢?
二进制、位运算、前缀和,差分。最好有线段树基础。
到现在,大家应该对树状数组有了一定的了解还不是很了解,那就来由我来给大家讲讲树状数组的知识吧!
2. 算法实现#
一个树状数组大概长这样(以区间和为例)
可以观察到,树状数组
比如
然后进行修改和查询操作的时候,可以利用
比如要将
查询也是如此,只要依次跳跃至查询节点即可。
int lb(int x) {
return x & -x;
}
单点修改实现:
void add(int x, int k) {
for (int i = x; i <= n; i += lb(i)) {
t[i] += k;
}
}
区间查询
int qry(int x) {
int res = 0;
for (int i = x; i; i -= lb(i)) {
res += t[i];
}
return res;
}
2.1 单点加区间和#
直接套用树状数组的单点加,区间和的操作即可。
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N = 5e5 + 10;
int n, m, t[N];
int lb(int x) {
return x & -x;
}
void add(int x, int k) {
for (int i = x; i <= n; i += lb(i)) {
t[i] += k;
}
}
int qry(int x) {
int res = 0;
for (int i = x; i; i -= lb(i)) {
res += t[i];
}
return res;
}
signed main() {
n = read(), m = read();
For(i,1,n) add(i, read());
while(m--) {
int op, x, y;
op = read(), x = read(), y = read();
if(op == 1) add(x, y);
else cout << qry(y) - qry(x - 1) << '\n';
}
return 0;
}
2.2 区间加单点和#
树状数组维护差分。
每一次操作在差分序列上的
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N = 5e5 + 10;
int n, m, t[N], a[N];
int lb(int x) {
return x & -x;
}
void add(int x, int k) {
for (int i = x; i <= n; i += lb(i)) {
t[i] += k;
}
}
int qry(int x) {
int res = 0;
for (int i = x; i; i -= lb(i)) {
res += t[i];
}
return res;
}
signed main() {
n = read(), m = read();
For(i,1,n) a[i] = read();
For(i,1,n) add(i, a[i] - a[i-1]);
while(m--) {
int op, x, y, k;
op = read();
if(op == 1) {
x = read(), y = read(), k = read();
add(x, k);
add(y+1, -k);
} else {
x = read();
cout << qry(x) << '\n';
}
}
return 0;
}
2.3 区间加区间和#
维护差分序列
然后拆式子
开两个树状数组分别维护
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 1e5 + 10;
int n, m, a[N], t1[N], t2[N];
int lb(int x) {
return x & -x;
}
void add(int x, int k) {
for (int i = x; i <= n; i += lb(i)) {
t1[i] += k;
t2[i] += k * (x - 1);
}
}
int qry(bool f, int x) {
int ans = 0;
for (int i = x; i; i -= lb(i)) {
ans += (!f ? t1[i] : t2[i]);
}
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
For(i,1,n) {
cin >> a[i];
add(i, a[i] - a[i-1]);
}
while(m--) {
int op, x, y, k;
cin >> op >> x >> y;
if(op == 1) {
cin >> k;
add(x, k);
add(y+1, -k);
} else {
cout << (y * qry(0, y) - qry(1, y)) - (((x-1) * qry(0, x-1)) - qry(1, x-1)) << '\n';
}
}
return 0;
}
作者:Daniel-yao
出处:https://www.cnblogs.com/Daniel-yao/p/17258699.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】