0.前情提要
不好意思托更了辣木久…
上一回,TengMax君讲了简单的子区间求和,
运用了一维预处理解决了难题。
最终布置了一个练习
1.习题讲解
便是
洛谷U97755 【模板】前缀和
U97755 【模板】前缀和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
前缀和应该没有什么大碍(详见子区间求和上);
问题是前缀异或
然而,这里我们需要运用异或的两个性质,
a
⨁
a
=
0
a \bigoplus a=0
a⨁a=0
0 ⨁ a = a 0 \bigoplus a=a 0⨁a=a
我们用前缀和的方法,
所以
[
l
,
r
]
[l,r]
[l,r]异或的结果为
d
i
r
[
r
]
⨁
d
i
r
[
l
−
1
]
dir[r] \bigoplus dir[l - 1]
dir[r]⨁dir[l−1]
所以代码为
#include <bits/stdc++.h>
#define ios ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define ll long long
using namespace std;
ll n, m;
ll a[2000005];
ll ts[2000005];
ll ty[2000005];
int main() {
scanf("%lld%lld", &n, &m);
for (register ll i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
ts[i] = ts[i - 1] + a[i];
ty[i] = ty[i - 1] ^ a[i];
}
while (m--) {
ll type;
ll a1, a2;
scanf("%lld%lld%lld", &type, &a1, &a2);
if (type == 1) {
printf("%lld\n", ts[a2] - ts[a1 - 1]);
}
else {
printf("%lld\n", (ty[a2] ^ ty[a1 - 1]));
}
}
return 0;
}
过啦~
2.正题引入
今天要TengMax君讲的与前缀和脱不了干系,
就是前缀和的逆运算——
差分。
3.数学定义
差分到底是什么意思?
我们假设有一个原数组
a
[
M
a
x
n
]
:
i
n
t
a[Maxn]:int
a[Maxn]:int
将它进行差分,得到了蜜(谜)汁(之)数组
再将蜜(谜)汁(之)数组进行一次前缀和,
这次的前缀和数组,就是原数组。
而这个差分,就是要解决进行一番修改之后的
子区间求和。
4.代码以及解释
那这个进行一番修改之后的子区间求和,
到底是怎么修改?
首先有一个原数组:
a
[
M
a
x
n
]
:
i
n
t
a[Maxn]:int
a[Maxn]:int
接着有一个操作数 m m m
接下来 m m m个操作里
每个操作有3个数: [ l , r , x ] [l, r, x] [l,r,x]
代表 a 数组从 l 到 r 的所有的数加 x a数组从l到r的所有的数加x a数组从l到r的所有的数加x,
最后又有 q q q个询问数,
接着就与上一回的题目一样了
首先,最能想到的就是暴力操作+前缀和,
解法一
那就是这样:
void ArrSum() {
int n;
cin >> n;
int a[n + 1];
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int q, p;
cin >> p;
while (p--) {
int x, y, z;
cin >> x >> y >> z;
for (int i = x; i <= y; i++) {
a[i] += z;
}
}
for (int i = 1; i <= n; i++) {
a[i] += a[i - 1];
}
cin >> q;
while (q--) {
int x, y;
cin >> x >> y;
cout << a[y] - a[x - 1] << endl;
}
}
呃……
时间复杂度 O ( n p ) O(np) O(np)
空间复杂度 O ( n ) O(n) O(n)
好像哪里有点不太对,不妨
优化一下
我真的会谢
不过在此之前,咱先把蜜(谜)汁(之)数组是啥解决了来
插:差分实现
我们不妨假设,差分数组
d
[
M
a
x
n
]
:
i
n
t
d[Maxn]:int
d[Maxn]:int
的值为
d
=
{
1
,
1
,
1
,
1
,
1
}
d=\{1,1,1,1,1\}
d={1,1,1,1,1}
根据差分的定义,写出原数组
a
=
{
1
,
2
,
3
,
4
,
5
}
a=\{1,2,3,4,5\}
a={1,2,3,4,5}
所以
d
和
a
d和a
d和a的关系就是
d
i
=
a
i
−
a
i
−
1
d_i=a_i-a_{i-1}
di=ai−ai−1
我们再动动
a
a
a数组,看看会咋样
比如我们将 [ 2 , 4 ] [2,4] [2,4]区间的内容全部加 4 4 4
所以,
a
1
=
{
1
,
6
,
7
,
8
,
5
}
a1=\{1,6,7,8,5\}
a1={1,6,7,8,5}
根据我们推导的公式得出差分数组
d
1
=
{
1
,
5
,
1
,
1
,
−
3
}
d1=\{1,5,1,1,-3\}
d1={1,5,1,1,−3}
相比于
d
d
d数组
d
1
d1
d1数组只是
2
的位置加
4
2的位置加4
2的位置加4和
5
的位置减
4
5的位置减4
5的位置减4
现在咱就找着了进行一番修改之后的子区间求和
那我们再来回顾例题
解法二
根据我们推导的公式
可以马上写出代码:
void ArrSum() {
int n;
cin >> n;
int a[n + 1], d[n + 1];
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
d[i] = a[i] - a[i - 1];
}
int q, p;
cin >> p;
while (p--) {
int x, y, z;
cin >> x >> y >> z;
d[x] += z;//利用查分数组进行区间修改
d[y + 1] -= z;
}
for (int i = 1; i <= n; i++) {
d[i] += d[i - 1];
}//执行完这个循环后d就是原数组
for (int i = 1; i <= n; i++) {
d[i] += d[i - 1];
}//执行完这个循环后d就是前缀和数组
cin >> q;
while (q--) {
int x, y;
cin >> x >> y;
cout << d[y] - d[x - 1] << endl;
}
}
时间复杂度 O ( n ) O(n) O(n)
空间复杂度 O ( n ) O(n) O(n)
嗯,不愧是我
5.练习
嗯,就它了
P2367 语文成绩 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
See You
欲知后事如何,且听下回分解
😛