题目地址:
https://www.luogu.com.cn/problem/P3368
题目描述:
如题,已知一个数列,你需要进行下面两种操作:将某区间每一个数数加上
x
x
x;求出某一个数的值。
输入格式:
第一行包含两个整数
N
N
N、
M
M
M,分别表示该数列数字的个数和操作的总个数。第二行包含
N
N
N个用空格分隔的整数,其中第
i
i
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的结果。
数据规模与约定:
对于
30
%
30\%
30%的数据:
N
≤
8
,
M
≤
10
N≤8,M≤10
N≤8,M≤10;
对于
70
%
70\%
70%的数据:
N
≤
10000
,
M
≤
10000
N≤10000,M≤10000
N≤10000,M≤10000;
对于
100
%
100\%
100%的数据:
1
≤
N
,
M
≤
500000
1≤N,M≤500000
1≤N,M≤500000,
1
≤
x
,
y
≤
n
1≤x,y≤n
1≤x,y≤n,保证任意时刻序列中任意元素的绝对值都不大于
2
30
2^{30}
230。
这两个操作容易让人想到差分数组,因为差分数组对操作1的效率很高,是
O
(
1
)
O(1)
O(1)。例如对于数组
[
1
,
5
,
4
,
2
,
3
]
[1,5,4,2,3]
[1,5,4,2,3],我们构造差分数组:
[
1
,
4
,
−
1
,
−
2
,
1
]
[1,4,-1,-2,1]
[1,4,−1,−2,1]假设我们要将原数组的第2到第4个数每个数都加2,得
[
1
,
7
,
6
,
4
,
3
]
[1,7,6,4,3]
[1,7,6,4,3]对于差分数组来说,只有第2和第5个数变了(因为原数组只有第2个数和第1个数的差,以及第5与第4的差变了),我们只需要将差分数组的第2个数加2,第5个数减2即可:
[
1
,
6
,
−
1
,
−
2
,
−
1
]
[1,6,-1,-2,-1]
[1,6,−1,−2,−1]但差分数组对操作2的复杂度则是
O
(
n
)
O(n)
O(n),我们需要将差分数组从开头直到第
i
i
i个数的和算出来,也就是要算差分数组的前缀和。对于前缀和,树状数组(Fenwick Tree)有着不错的效率,它支持
O
(
log
n
)
O(\log n)
O(logn)的更新和查询。对树状数组的详细介绍,请看https://blog.csdn.net/qq_46105170/article/details/103870987。思路就很简单了:
1、对原数组的差分数组建Fenwick Tree
2、操作1对应着让Fenwick Tree的第
a
a
a个数加
x
x
x,并对第
b
+
1
b+1
b+1个数减
x
x
x
3、操作2对应着查差分数组前
a
a
a个数前缀和,可以利用Fenwick Tree查
代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 5e5 + 10;
int n, m;
int tr[N], a[N];
int lowbit(int x) {
return x & -x;
}
void add(int x, int v) {
while (x <= n) {
tr[x] += v;
x += lowbit(x);
}
}
int sum(int x) {
int res = 0;
while (x) {
res += tr[x];
x -= lowbit(x);
}
return res;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
// 用差分来建树
for (int i = 1; i <= n; i++) add(i, a[i] - a[i - 1]);
while (m--) {
int type, x, y, k;
scanf("%d", &type);
if (type == 1) {
scanf("%d%d%d", &x, &y, &k);
add(x, k), add(y + 1, -k);
} else {
scanf("%d", &x);
printf("%d\n", sum(x));
}
}
return 0;
}
预处理时间复杂度 O ( n log n ) O(n\log n) O(nlogn),每次询问 O ( log n ) O(\log n) O(logn),空间 O ( n ) O(n) O(n)。