【模板】树状数组 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
-
将某一个数加上 x x x
-
求出某区间每一个数的和
输入格式
第一行包含两个正整数 n , m n,m n,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。
接下来 m m m 行每行包含 3 3 3 个整数,表示一个操作,具体如下:
-
1 x k
含义:将第 x x x 个数加上 k k k -
2 x y
含义:输出区间 [ x , y ] [x,y] [x,y] 内每个数的和
输出格式
输出包含若干行整数,即为所有操作 2 2 2 的结果。
样例 #1
样例输入 #1
5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4
样例输出 #1
14
16
提示
【数据范围】
对于
30
%
30\%
30% 的数据,
1
≤
n
≤
8
1 \le n \le 8
1≤n≤8,
1
≤
m
≤
10
1\le m \le 10
1≤m≤10;
对于
70
%
70\%
70% 的数据,
1
≤
n
,
m
≤
1
0
4
1\le n,m \le 10^4
1≤n,m≤104;
对于
100
%
100\%
100% 的数据,
1
≤
n
,
m
≤
5
×
1
0
5
1\le n,m \le 5\times 10^5
1≤n,m≤5×105。
数据保证对于任意时刻, a a a 的任意子区间(包括长度为 1 1 1 和 n n n 的子区间)和均在 [ − 2 31 , 2 31 ) [-2^{31}, 2^{31}) [−231,231) 范围内。
样例说明:
故输出结果14、16
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, m;
int a[N], tr[N];
int lowbit(int x)
{
return x & -x;
}
void add(int x, int v) //单点修改
{
for(int i = x; i <= n; i += lowbit(i)) tr[i] += v;
}
int query(int x) //区间查询
{
int res = 0;
for(int i = x; i; i -= lowbit(i)) res += tr[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];
//构造树状数组就是从零到有的添加过程
for(int i = 1; i <= n; i ++) add(i, a[i]);
//接下来进行操作
while(m --)
{
int temp, a, b;
cin >> temp >> a >> b;
if(temp == 1) add(a, b);
if(temp == 2) cout << query(b) - query(a - 1) << endl; //就是前缀和
}
return 0;
}
【模板】树状数组 2
题目描述
如题,已知一个数列,你需要进行下面两种操作:
-
将某区间每一个数加上 x x x;
-
求出某一个数的值。
输入格式
第一行包含两个整数 N N N、 M M M,分别表示该数列数字的个数和操作的总个数。
第二行包含 N N N 个用空格分隔的整数,其中第 i i i 个数字表示数列第 $i $ 项的初始值。
接下来 M M M 行每行包含 2 2 2 或 4 4 4个整数,表示一个操作,具体如下:
操作
1
1
1: 格式:1 x y k
含义:将区间
[
x
,
y
]
[x,y]
[x,y] 内每个数加上
k
k
k;
操作
2
2
2: 格式:2 x
含义:输出第
x
x
x 个数的值。
输出格式
输出包含若干行整数,即为所有操作 2 2 2 的结果。
样例 #1
样例输入 #1
5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4
样例输出 #1
6
10
提示
样例 1 解释:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eCTm1QY1-1677763437993)(https://cdn.luogu.com.cn/upload/pic/2258.png)]
故输出结果为 6、10。
数据规模与约定
对于 30 % 30\% 30% 的数据: N ≤ 8 N\le8 N≤8, M ≤ 10 M\le10 M≤10;
对于 70 % 70\% 70% 的数据: N ≤ 10000 N\le 10000 N≤10000, M ≤ 10000 M\le10000 M≤10000;
对于 100 % 100\% 100% 的数据: 1 ≤ N , M ≤ 500000 1 \leq N, M\le 500000 1≤N,M≤500000, 1 ≤ x , y ≤ n 1 \leq x, y \leq n 1≤x,y≤n,保证任意时刻序列中任意元素的绝对值都不大于 2 30 2^{30} 230。
来介绍一下差分
设数组a[]={1,6,8,5,10},那么差分数组b[]={1,5,2,-3,5}
也就是说b[i]=a[i]-a[i-1];(a[0]=0;),那么a[i]=b[1]+…+b[i];(这个很好证的)。
假如区间[2,4]都加上2的话
a数组变为a[]={1,8,10,7,10},b数组变为b={1,7,2,-3,3};
发现了没有,b数组只有b[2]和b[5]变了,因为区间[2,4]是同时加上2的,所以在区间内b[i]-b[i-1]是不变的.
所以对区间[x,y]进行修改,只用修改b[x]与b[y+1]:
b[x]=b[x]+k;b[y+1]=b[y+1]-k;
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, m;
int a[N], tr[N];
int lowbit(int x)
{
return x & -x;
}
void add(int x, int v) //单点修改
{
for(int i = x; i <= n; i += lowbit(i)) tr[i] += v;
}
int query(int x) //区间查询
{
int res = 0;
for(int i = x; i; i -= lowbit(i)) res += tr[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];
//接下来进行操作
while(m --)
{
int temp, x, y, k;
cin >> temp;
if(temp == 1)
{
cin >> x >> y >> k;
add(x, k);
add(y + 1, -k); //区间修改
}
if(temp == 2)
{
cin >> x;
cout << query(x) + a[x] << endl; //单点查询
}
}
return 0;
}