介绍
树状数组中存的元素是由该元素下标的每一个lowbit的数再原数组中作为下标的元素的和组成,并且下标都是从1开始的
如下图
代码
模板1(单点修改+区间查询)
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int tree[N];
int n, m;
void add(int a, int b)//添加一个数只会影响后面的数不会影响前面的数,所以我们通过lowbit去枚举后面的数从而更新树状数组
{
for(int i = a; i <= n; i += i & -i)
{
tree[i] += b;
}
}
int ask(int a)//这边的查询功能是要查询[1, a]这个区间(即求前缀和)
{
int res = 0;
for(int i = a; i; i -= i & -i)
{
res += tree[i];
}
return res;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
int tmp;
cin >> tmp;
add(i, tmp);
}
while(m--)
{
int x, y;
char op;
cin >> op >> x >> y;
if(op == '1')
{
add(x, y);
}
else
{
cout << ask(y) - ask(x - 1) << endl;
}
}
return 0;
}
模板2(单点查询+单点修改+区间查询+区间修改)
要想使用区间修改这里我们就需要用到差分来实现
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int a[N], c1[N], c2[N];
int n, m;
//区间修改[l, r]就是用add(r, x) - add(l - 1, x)
//单点修改就是add(p, x), add(p + 1, -x)
void add(int p, int x)
{
for(int i = p; i <= n; i += i & -i)
{
c1[i] += x, c2[i] += p * x;
}
}
int ask1(int p)
{
int res = 0;
for(int i = p; i; i -= i & -i)
{
res += c1[i];
}
return res;
}
//区间求和
int ask(int p)
{
int res = 0;
for(int i = p; i; i -= i & -i)
{
res += c1[i] * (p + 1) - c2[i];
}
return res;
}
//如果想要查询某个点则就可以直接想模板1那样对c1进行查询,这边用到c2是想要 用两个树状数组来维护区间求和的操作
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
add(i, a[i]), add(i + 1, -a[i]);//其实这样子的话,用a[N]就没有意义了,直接拿一个临时变量就行了
//add(i, a[i] - a[i - 1]);
}
while(m--)
{
int op;
cin >> op;
if(op == 1)
{
int x, y, k;
cin >> x >> y >> k;
add(x, k), add(y + 1, -k);
}
else
{
int x;
cin >> x;
cout << ask1(x) << endl;
}
}
return 0;
}
例题
无聊的数列
题目背景
无聊的 YYB 总喜欢搞出一些正常人无法搞出的东西。有一天,无聊的 YYB 想出了一道无聊的题:无聊的数列。。。(K峰:这题不是傻X题吗)
题目描述
维护一个数列 a i a_i ai,支持两种操作:
-
1 l r K D
:给出一个长度等于 r − l + 1 r-l+1 r−l+1 的等差数列,首项为 K K K,公差为 D D D,并将它对应加到 [ l , r ] [l,r] [l,r] 范围中的每一个数上。即:令 a l = a l + K , a l + 1 = a l + 1 + K + D … a r = a r + K + ( r − l ) × D a_l=a_l+K,a_{l+1}=a_{l+1}+K+D\ldots a_r=a_r+K+(r-l) \times D al=al+K,al+1=al+1+K+D…ar=ar+K+(r−l)×D。 -
2 p
:询问序列的第 p p p 个数的值 a p a_p ap。
输入格式
第一行两个整数数 n , m n,m n,m 表示数列长度和操作个数。
第二行 n n n 个整数,第 i i i 个数表示 a i a_i ai。
接下来的 m m m 行,每行先输入一个整数 o p t opt opt。
若 o p t = 1 opt=1 opt=1 则再输入四个整数 l r K D l\ r\ K\ D l r K D;
若 o p t = 2 opt=2 opt=2 则再输入一个整数 p p p。
输出格式
对于每个询问,一行一个整数表示答案。
样例 #1
样例输入 #1
5 2
1 2 3 4 5
1 2 4 1 2
2 3
样例输出 #1
6
提示
数据规模与约定
对于 100 % 100\% 100% 数据, 0 ≤ n , m ≤ 1 0 5 , − 200 ≤ a i , K , D ≤ 200 , 1 ≤ l ≤ r ≤ n , 1 ≤ p ≤ n 0\le n,m \le 10^5,-200\le a_i,K,D\le 200, 1 \leq l \leq r \leq n, 1 \leq p \leq n 0≤n,m≤105,−200≤ai,K,D≤200,1≤l≤r≤n,1≤p≤n。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll n, m;
ll a[N], d[N], bit1[N], bit2[N];
void add(ll x, ll del)
{
for(int i = x; i <= n; i += i & -i)
{
bit1[i] += del;
bit2[i] += del * x;
}
}
ll query(ll x)
{
ll res = 0;
for(int i = x; i; i -= i & -i)
{
res += (x + 1) * bit1[i] - bit2[i];
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
d[i] = a[i] - a[i - 1];
add(i, d[i] - d[i - 1]);
}
for(int i = 1; i <= m; i++)
{
ll op;
cin >> op;
if(op == 1)
{
ll l, r, k, D;
cin >> l >> r >> k >> D;
add(l, k), add(l + 1, D - k), add(r + 1, -(r - l + 1) * D - k), add(r + 2, k + (r - l) * D);
}
else
{
ll p;
cin >> p;
cout << query(p) << endl;
}
}
return 0;
}