所以,我们发现,前缀和多开了一个数组,以达到优化计算时间的效果,是典型的 以空间换时间 的算法。
2、代码实现
接下来做道前缀和例题练练手。
链接:795. 前缀和
描述:
输入一个长度为
n
n
n 的整数序列。
接下来再输入
m
m
m 个询问,每个询问输入一对
l
,
r
l, r
l,r。
对于每个询问,输出原序列中从第
l
l
l 个数到第
r
r
r 个数的和。
输入格式:
第一行包含两个整数
n
n
n 和
m
m
m 。
第二行包含
n
n
n 个整数,表示整数数列。
接下来
m
m
m 行,每行包含两个整数
l
l
l 和
r
r
r ,表示一个询问的区间范围。
输出格式:
共
m
m
m 行,每行输出一个询问的结果。
数据范围:
- 1
≤
l
≤
r
≤
n
1≤l≤r≤n
1≤l≤r≤n
- 1
≤
n
,
m
≤
100000
1≤n,m≤100000
1≤n,m≤100000
- −
1000
≤
数列中元素的值
≤
1000
−1000 ≤ 数列中元素的值 ≤ 1000
−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
思路 :典型的前缀和例题,思路我们上面已经讲过一遍了,直接开始写代码:
二、二维前缀和
二维前缀和是一维前缀和的加强。
从前只能求一维序列的前缀和,现在能求二维,也就是矩阵的前缀和。
1、算法推导
接下来看 二维前缀和数组 是如何构造的:
假设给定二维矩阵
a
和
s
a 和 s
a和s ,
s
s
s 为前缀和矩阵,这里我们设定一个前提:竖直方向我们统一称为长,横平的边我们统一称为宽,以便我们描述图形。
假如要求 **点
(
i
,
j
)
(i, j)
(i,j)** 的前缀和,那么对于图中,以
i
,
j
i, j
i,j 为长和宽的矩阵就是 点
(
i
,
j
)
(i, j)
(i,j) 的前缀和。
观察上图,可以得到面积公式:
一整块矩形面积
=
紫色面积
绿色面积
蓝色面积
红色面积
一整块矩形面积 = 紫色面积 + 绿色面积 + 蓝色面积 + 红色面积
一整块矩形面积=紫色面积+绿色面积+蓝色面积+红色面积
但是,对于矩阵的面积,有些地方是无法单独计算的,比如绿色区域,只能算以
i
−
1
i - 1
i−1 为长,
j
j
j 为高的面积。
所以,我们实际计算时,真正的计算公式 为:
一整块矩形面积
=
以
i
−
1
为长以
j
为高的矩形面积
以
i
为长以
j
−
1
为高的矩形面积
−
紫色面积
红色面积
一整块矩形面积 = 以\ i - 1 \ 为长以 \ j \ 为高的矩形面积 + 以\ i \ 为长以 \ j - 1 \ 为高的矩形面积 - 紫色面积 \ + \ 红色面积
一整块矩形面积=以 i−1 为长以 j 为高的矩形面积+以 i 为长以 j−1 为高的矩形面积−紫色面积 + 红色面积
以图表示:
至于为什么要加 紫色区域 呢?这是因为在加绿色矩阵和蓝色矩阵的时候,加了两次 紫色区域 ,所以需要去掉重复的这一块。
而这里的每一个与长和宽有直接关联的区域其实就是这一块区域的 前缀和 ,在
s
s
s 矩阵中都可以表示出来,而 红色小方格的面积 则是
a
a
a 矩阵当前位置的元素,所以我们可以得到 二维矩阵前缀和预处理公式 :
s
[
i
]
[
j
]
=
s
[
i
−
1
]
[
j
]
s
[
i
]
[
j
−
1
]
−
s
[
i
−
1
]
[
j
−
1
]
a
[
i
]
[
j
]
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j]
s[i][j]=s[i−1][j]+s[i][j−1]−s[i−1][j−1]+a[i][j]
那么我们构造过程的代码也就可以写出来了:
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
}
}
而对于二维前缀和,我们的题型通常是求 子矩阵的和 ,比如:
以我们平常的思维方式,就是 两层循环遍历一下 ,时间复杂度为
O
(
N
×
M
)
O(N \times M)
O(N×M)。
但是如果我们使用 前缀和 呢?实际上只需要
O
(
1
)
O(1)
O(1) 的时间复杂度,是非常快的,但是前提得有公式,所以我们得先把公式推导出来。
推导过程 :
我们再进行严格的区域划分,画一张较为详细的图:
同样的这里设定前提:竖直方向的边统一称为长,横平方向的边统一称为宽,以便我们描述图形。
根据上图,我们可以得出公式:
蓝色面积
=
以
x
2
为长,
y
2
为宽的区域面积
−
黄色面积
−
绿色面积
−
紫色面积
蓝色面积 = 以 \ x2 \ 为长,\ y2 \ 为宽的区域面积 - 黄色面积 - 绿色面积 - 紫色面积
蓝色面积=以 x2 为长, y2 为宽的区域面积−黄色面积−绿色面积−紫色面积
但是这里的区域面积也是不好算一小块的,区域的面积的长和宽要从
(
1
,
1
)
(1, 1)
(1,1) 出发,所以我们 真正的公式 为:
蓝色面积
=
以
x
2
为长
y
2
为宽的区域面积
−
以
x
1
−
1
为长
y
2
为宽的区域面积
−
以
x
2
为长
y
1
−
1
为宽的区域面积
紫色面积
蓝色面积 = 以 \ x2 \ 为长 \ y2 \ 为宽的区域面积 - 以 \ x1 - 1 为长\ y2 \ 为宽的区域面积 - 以 \ x2 \ 为长 \ y1 - 1 \ 为宽的区域面积 + 紫色面积
蓝色面积=以 x2 为长 y2 为宽的区域面积−以 x1−1为长 y2 为宽的区域面积−以 x2 为长 y1−1 为宽的区域面积+紫色面积
加上 紫色面积 的原因是,我们减去了两块 紫色面积 ,需要补上一个。
同样的,这里每块区域的面积,实际上就是 前缀和 ,比如 紫色面积就是
a
a
a 矩阵中以
x
1
−
1
x1- 1
x1−1 为长,
y
1
−
1
y1 - 1
y1−1 的区域面积,前缀和形式就直接为
s
[
x
1
−
1
]
[
y
1
−
1
]
s[x1 -1][y1 - 1]
s[x1−1][y1−1]。
所以这里写出我们的 查询公式 :
蓝色面积
=
s
[
x
2
]
[
y
2
]
−
s
[
x
1
−
1
]
[
y
2
]
−
s
[
x
2
]
[
y
1
−
1
]
s
[
x
1
−
1
]
[
y
1
−
1
]
蓝色面积 = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]
蓝色面积=s[x2][y2]−s[x1−1][y2]−s[x2][y1−1]+s[x1−1][y1−1]
到这里,我们的前缀和的核心公式都已经推导出来了,在做题目是就很方便了,只需要:预处理 + 查询 。
2、代码实现
链接:796. 子矩阵的和
描述:
输入一个
n
n
n 行
m
m
m 列的整数矩阵,再输入
q
q
q 个询问,每个询问包含四个整数
x
1
,
y
1
,
x
2
,
y
2
x1,y1,x2,y2
x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式:
第一行包含三个整数
n
n
n,
m
m
m,
q
q
q 。
接下来
n
n
n 行,每行包含
m
m
m 个整数,表示整数矩阵。
接下来
q
q
q 行,每行包含四个整数
x
1
,
y
1
,
x
2
,
y
2
x1,y1,x2,y2
x1,y1,x2,y2 ,表示一组询问。
输出格式:
共
q
q
q 行,每行输出一个询问的结果。
数据范围:
- 1
≤
n
,
m
≤
1000
1≤n,m≤1000
1≤n,m≤1000
- 1
≤
q
≤
200000
1≤q≤200000
1≤q≤200000
- 1
≤
x
1
≤
x
2
≤
n
1≤x1≤x2≤n
1≤x1≤x2≤n
- 1
≤
y
1
≤
y
2
≤
m
1≤y1≤y2≤m
1≤y1≤y2≤m
- −
1000
≤
矩阵内元素的值
≤
1000
−1000≤矩阵内元素的值≤1000
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
思路:
上面公式我们推导过了,直接预处理 + 查询即可:
三、一维差分
其实博主觉得差分是一个很抽象的算法,我们可以构造差分数组算,同样的也可以通过另一种方式不构造数组求出结果。
至于为什么我会这么觉得,别急,我们慢慢来,先讲差分的思想再说~
1、算法推导
前面我们学了前缀和,现在又要学差分,它们之间有联系吗?
实际上可以简单推测一下,一个是求 ”和“ ,一个是求 ”差“ ,那 差分是不是就是前缀和的逆运算 ?答案是正确的,差分其实就是前缀和的一个反推。
对于差分算法我们一般得先 构造差分数组 ,假设现在有两个数组
a
和
b
a 和\ b
a和 b :
我们需要构造
b
b
b 为差分数组,**使得
a
a
a 数组是
b
b
b 的前缀和数组** ,也就是说
a
a
a 中每一个数据,都是
b
b
b 中在包括这个位置之前所有的数据的和。这时
b
b
b 被称为
a
a
a 的差分。
**所以
b
b
b 数组每个元素无非就是
a
a
a 数组的每一个元素与其前一个元素的差嘛!**
为什么这么说?我们来算一下就知道了:
a
[
i
]
=
b
[
1
]
b
[
2
]
.
.
.
b
[
i
]
a
[
i
−
1
]
=
b
[
1
]
b
[
2
]
.
.
.
b
[
i
−
1
]
a
[
i
]
−
a
[
i
−
1
]
=
(
b
[
i
]
b
[
i
−
1
]
.
.
.
b
[
2
]
b
[
1
]
)
−
(
b
[
i
−
1
]
.
.
.
b
[
2
]
b
[
1
]
)
=
b
[
i
]
\begin{align} & a[i] \ \ \ \ \ \ \ = b[1] + b[2] + … + b[i] \newline \newline & a[i - 1] = b[1] + b[2] + … + b[i - 1] \newline \newline & a[i] - a[i - 1] = ({\color{red}b[i]} + b[i - 1] + … + b[2] + b[1]) - (b[i - 1] + … + b[2] + b[1]) = {\color{red}b[i]} \end{align}
a[i] =b[1]+b[2]+…+b[i]a[i−1]=b[1]+b[2]+…+b[i−1]a[i]−a[i−1]=(b[i]+b[i−1]+…+b[2]+b[1])−(b[i−1]+…+b[2]+b[1])=b[i]
所以 **差分数组
b
b
b 的构造方式如下** :
一维差分数组构造方式:
{
b
[
1
]
=
a
[
1
]
b
[
2
]
=
a
[
2
]
−
a
[
1
]
b
[
3
]
=
a
[
3
]
−
a
[
2
]
.
.
.
b
[
n
]
=
a
[
n
]
−
a
[
n
−
1
]
一维差分数组构造方式: \begin{cases} b[1] = a[1]\\ b[2] = a[2] - a[1]\\ b[3] = a[3] - a[2]\\ …\\ b[n] = a[n] - a[n - 1] \end{cases}
一维差分数组构造方式:⎩
⎨
⎧b[1]=a[1]b[2]=a[2]−a[1]b[3]=a[3]−a[2]…b[n]=a[n]−a[n−1]
那么这里的 构造过程 实际上就是遍历一遍
a
a
a 数组,就可以构造出
b
b
b ,**时间复杂度为
O
(
N
)
O(N)
O(N)** 。我们再看看代码怎么写:
for (int i = 1; i <= n; i++) {
b[i] = a[i] - a[i - 1];
}
讲完了构造,我们再看看差分这个算法为了解决什么问题:
差分算法是为了解决让 **序列中某段区间
[
l
,
r
]
[l, r]
[l,r] 加上一个 常数
c
c
c** 的问题。
放到平时,那么我们肯定是遍历一遍,然后把数据
c
c
c 加上,时间复杂度为
O
(
N
)
O(N)
O(N) ,但是如果使用差分呢?
**假设现在
a
a
a 是原数组,差分数组
b
b
b 已经求好了,此时要让
a
a
a 数组
[
l
,
r
]
[l, r]
[l,r] 区间内
c
+c
+c**,**只需要让
b
[
l
]
=
c
,让
b
[
r
1
]
−
=
c
\color{red}b[l] += c,让 b[r + 1] -=c
b[l]+=c,让b[r+1]−=c 即可**,**时间复杂度为
O
(
1
)
O(1)
O(1)** 。
如下图:
但是为什么这样就可以了,它的原理是什么?我们还得继续探究:
由于
a
a
a 数组是
b
b
b 数组的前缀和,所以让
b
[
l
]
=
c
\color{red}b[l] += c
b[l]+=c ,就会造成如下结果:
b
加上
c
后:
b
[
l
]
=
b
[
l
]
′
c
a
[
l
]
′
=
b
[
1
]
b
[
2
]
.
.
.
b
[
l
]
′
↓
a
[
l
]
=
b
[
1
]
b
[
2
]
.
.
.
b
[
l
]
′
c
↓
当前
a
[
l
]
已经发生了改变:
a
[
l
]
=
a
[
l
]
′
c
↓
所以接下来的
a
数组中的元素都会加上
c
!
↓
a
[
l
1
]
=
a
[
l
]
′
c
b
[
l
1
]
↓
.
.
.
↓
a
[
n
]
=
a
[
n
−
1
]
′
c
b
[
n
]
\begin{align} &b \ 加上 \ c \ 后:b[l] = b[l]’ + c \\ &a[l]’ = b[1] + b[2] + … + b[l]’ \ &\downarrow \ &a[l] = b[1] + b[2] + … + \color{red}b[l]’ + c \ &\downarrow \ &当前 a[l] 已经发生了改变:\color{red}a[l] = a[l]’ + c \ &\downarrow \ &所以接下来的 a 数组中的元素都会加上 c ! \ &\downarrow \ &a[l + 1] = {\color{red}a[l]’ + c} + b[l + 1] \ &\downarrow \ &… \ &\downarrow \ &a[n] = {\color{red}a[n - 1]’ + c} + b[n] \end{align}
b 加上 c 后:b[l]=b[l]′+ca[l]′=b[1]+b[2]+…+b[l]′↓a[l]=b[1]+b[2]+…+b[l]′+c↓当前a[l]已经发生了改变:a[l]=a[l]′+c↓所以接下来的a数组中的元素都会加上c!↓a[l+1]=a[l]′+c+b[l+1]↓…↓a[n]=a[n−1]′+c+b[n]
同理,对于
b
[
r
1
]
−
=
c
\color{red}b[r + 1] -= c
b[r+1]−=c 也可以推导,下面我就简写一下了:
b
[
r
1
]
=
b
[
r
1
]
′
−
c
a
[
r
1
]
=
a
[
r
]
b
[
r
1
]
′
−
c
↓
a
[
r
1
]
=
a
[
r
1
]
′
−
c
↓
a
[
r
2
]
=
a
[
r
1
]
′
−
c
b
[
r
2
]
↓
.
.
.
↓
a
[
n
]
=
a
[
n
−
1
]
′
−
c
b
[
n
]
\begin{align} &b[r + 1] = b[r + 1]’ -c \\ &a[r + 1] = a[r] + b[r + 1]’ - c \ &\downarrow \ &a[r + 1] = a[r + 1]’ - c \ &\downarrow \ &a[r + 2] = a[r + 1]’ - c + b[r + 2] \ &\downarrow \ &… \ &\downarrow \ &a[n] = a[n - 1]’ - c + b[n] \end{align}
b[r+1]=b[r+1]′−ca[r+1]=a[r]+b[r+1]′−c↓a[r+1]=a[r+1]′−c↓a[r+2]=a[r+1]′−c+b[r+2]↓…↓a[n]=a[n−1]′−c+b[n]
**由此,得证只要让
b
[
l
]
=
c
,让
b
[
r
1
]
−
=
c
\color{red}b[l] += c,让 b[r + 1] -=c
b[l]+=c,让b[r+1]−=c,就可以使
a
a
a 数组中
[
l
.
r
]
[l. r]
[l.r] 区间内元素加上 常数
c
c
c 。**
2、代码实现
高能预警,代码实现这块就是博主觉得比较抽象,但很神奇的地方。
先上题目 ~
链接:797.差分
描述:
输入一个长度为
n
n
n 的整数序列。
接下来输入
m
m
m 个操作,每个操作包含三个整数
l
,
r
,
c
l,r,c
l,r,c 表示将序列中
[
l
,
r
]
[l,r]
[l,r] 之间的每个数加上
c
c
c 。
请你输出进行完所有操作后的序列。
输入格式:
第一行包含两个整数
n
n
n 和
m
m
m 。
第二行包含
n
n
n 个整数,表示整数序列。
接下来
m
m
m 行,每行包含三个整数
l
,
r
,
c
l,r,c
l,r,c 表示一个操作。
输出格式:
共一行,包含
n
n
n 个整数,表示最终序列。
数据范围:
- 1
≤
n
,
m
≤
100000
1≤n,m≤100000
1≤n,m≤100000
- 1
≤
l
≤
r
≤
n
1≤l≤r≤n
1≤l≤r≤n
- −
1000
≤
c
≤
1000
−1000≤c≤1000
−1000≤c≤1000
- −
1000
≤
整数序列中元素的值
≤
1000
−1000≤整数序列中元素的值≤1000
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
代码1:
这段代码就是我们上方 算法推导的完全复刻 ,先构造差分矩阵
b
b
b ,再根据方法对区间进行操作。
对
b
b
b 数组完成计算之后,对齐求一下 前缀和 ,将数据存到
a
a
a 数组中。
代码2:
但是其实 y 老师讲的其实不是这种方法,他用了更加巧妙的一个方式。
y 老师的思路,是将
a
a
a 数组的所有元素全部假定为 0 ,用了一个 insert
函数,不考虑构造,通过对小区间的插入,和对
[
l
,
r
]
[l, r]
[l,r] 区间的处理,直接完成了对区间
[
l
,
r
]
\color{red}[l, r]
[l,r] 的运算。
它的思路是 单个小区间,也可以说是相同区间 进行数据之间的增加,比如:
a
1
a
2
a
3
.
.
.
a
n
↓
[
1
,
1
]
a
[
1
]
[
2
,
2
]
a
[
2
]
.
.
.
[
n
,
n
]
a
[
n
]
a1 \ a2 \ a3 \ … an \ \downarrow \ [1, 1] + a[1] \ \ \ \ [2, 2] + a[2] \ \ \ \ … \ \ \ \ [n, n] + a[n]
a1 a2 a3 …an↓[1,1]+a[1] [2,2]+a[2] … [n,n]+a[n]
每个数据一开始看做 0 ,这样子就像对每个值进行 +c
,再进行之后
[
l
,
r
]
[l, r]
[l,r] 区间的操作,直接求出结果。
这样就忽略了 构建差分数组 ,直接从结果上求解,运用了 差分的特性:对差分数组求前缀和就算得原数组 ,从而直接求得结果。
先来看一眼代码:
我第一眼看到这个代码我就觉得很惊艳,这是一个很巧妙的做法,但是过一会我就十分疑惑 :
如果我不从最终结果上来看,我就是要看 过程 呢?差分之前说过,第一步就要构建 差分数组 ,之后才能进行求解,但是这里没有构建是怎么算的?
可能是博主比较无聊,就想了好一会,看着代码没想明白,后来把这个过程推了一遍,发现,这一过程真的十分妙!
在上图中,我给出 红色
方框的部分既可以看做对小区间进行数据插入,又可以看做是在构建差分数组。
为什么这么说?下面我们进行一下推导证明:
之间我们推导过如何构造
差分矩阵
b
差分矩阵 b
差分矩阵b ,这里再提一下,这里是 关键 :
一维差分数组构造方式:
{
b
[
1
]
=
a
[
1
]
b
[
2
]
=
a
[
2
]
−
a
[
1
]
b
[
3
]
=
a
[
3
]
−
a
[
2
]
.
.
.
b
[
n
]
=
a
[
n
]
−
a
[
n
−
1
]
一维差分数组构造方式: \begin{cases} b[1] = a[1]\\ b[2] = a[2] - a[1]\\ b[3] = a[3] - a[2]\\ …\\ b[n] = a[n] - a[n - 1] \end{cases}
一维差分数组构造方式:⎩
⎨
⎧b[1]=a[1]b[2]=a[2]−a[1]b[3]=a[3]−a[2]…b[n]=a[n]−a[n−1]
由于
b
b
b 是全局数组,所以一开始元素都是为 0 的。
所以
b
\color{red}b
b 初始状态为:
0
0
0
0
0
0
1
2
3
4
5
6
\begin{align} &\ 0 \ \ \ \ 0 \ \ \ \ 0 \ \ \ \ 0 \ \ \ \ 0 \ \ \ \ 0 \ \ & \ 1 \ \ \ \ 2 \ \ \ \ 3 \ \ \ \ 4 \ \ \ \ 5 \ \ \ \ 6 \end{align}
0 0 0 0 0 0 1 2 3 4 5 6
**注:这里第一行为
b
b
b 数组,第二行为 下标
,由于 LaTeX
我用的还不是很熟练,所以还不能很好的控制对齐和缩进,加上标识这个就不对齐了,效果不太好,所以就省略了~大家看到这条之后委屈一下hh,我会尽量说明白的。**
现在开始模拟这一过程:
- 第一次循环,
insert(1, 1, a[1])
,对
[
1
,
1
]
[1, 1]
[1,1] 进行
a
[
1
]
a[1]
a[1] 元素的插入,插入之后结果:
a
[
1
]
−
a
[
1
]
0
0
0
0
1
2
3
4
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
−1000≤整数序列中元素的值≤1000
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
代码1:
这段代码就是我们上方 算法推导的完全复刻 ,先构造差分矩阵
b
b
b ,再根据方法对区间进行操作。
对
b
b
b 数组完成计算之后,对齐求一下 前缀和 ,将数据存到
a
a
a 数组中。
代码2:
但是其实 y 老师讲的其实不是这种方法,他用了更加巧妙的一个方式。
y 老师的思路,是将
a
a
a 数组的所有元素全部假定为 0 ,用了一个 insert
函数,不考虑构造,通过对小区间的插入,和对
[
l
,
r
]
[l, r]
[l,r] 区间的处理,直接完成了对区间
[
l
,
r
]
\color{red}[l, r]
[l,r] 的运算。
它的思路是 单个小区间,也可以说是相同区间 进行数据之间的增加,比如:
a
1
a
2
a
3
.
.
.
a
n
↓
[
1
,
1
]
a
[
1
]
[
2
,
2
]
a
[
2
]
.
.
.
[
n
,
n
]
a
[
n
]
a1 \ a2 \ a3 \ … an \ \downarrow \ [1, 1] + a[1] \ \ \ \ [2, 2] + a[2] \ \ \ \ … \ \ \ \ [n, n] + a[n]
a1 a2 a3 …an↓[1,1]+a[1] [2,2]+a[2] … [n,n]+a[n]
每个数据一开始看做 0 ,这样子就像对每个值进行 +c
,再进行之后
[
l
,
r
]
[l, r]
[l,r] 区间的操作,直接求出结果。
这样就忽略了 构建差分数组 ,直接从结果上求解,运用了 差分的特性:对差分数组求前缀和就算得原数组 ,从而直接求得结果。
先来看一眼代码:
我第一眼看到这个代码我就觉得很惊艳,这是一个很巧妙的做法,但是过一会我就十分疑惑 :
如果我不从最终结果上来看,我就是要看 过程 呢?差分之前说过,第一步就要构建 差分数组 ,之后才能进行求解,但是这里没有构建是怎么算的?
可能是博主比较无聊,就想了好一会,看着代码没想明白,后来把这个过程推了一遍,发现,这一过程真的十分妙!
在上图中,我给出 红色
方框的部分既可以看做对小区间进行数据插入,又可以看做是在构建差分数组。
为什么这么说?下面我们进行一下推导证明:
之间我们推导过如何构造
差分矩阵
b
差分矩阵 b
差分矩阵b ,这里再提一下,这里是 关键 :
一维差分数组构造方式:
{
b
[
1
]
=
a
[
1
]
b
[
2
]
=
a
[
2
]
−
a
[
1
]
b
[
3
]
=
a
[
3
]
−
a
[
2
]
.
.
.
b
[
n
]
=
a
[
n
]
−
a
[
n
−
1
]
一维差分数组构造方式: \begin{cases} b[1] = a[1]\\ b[2] = a[2] - a[1]\\ b[3] = a[3] - a[2]\\ …\\ b[n] = a[n] - a[n - 1] \end{cases}
一维差分数组构造方式:⎩
⎨
⎧b[1]=a[1]b[2]=a[2]−a[1]b[3]=a[3]−a[2]…b[n]=a[n]−a[n−1]
由于
b
b
b 是全局数组,所以一开始元素都是为 0 的。
所以
b
\color{red}b
b 初始状态为:
0
0
0
0
0
0
1
2
3
4
5
6
\begin{align} &\ 0 \ \ \ \ 0 \ \ \ \ 0 \ \ \ \ 0 \ \ \ \ 0 \ \ \ \ 0 \ \ & \ 1 \ \ \ \ 2 \ \ \ \ 3 \ \ \ \ 4 \ \ \ \ 5 \ \ \ \ 6 \end{align}
0 0 0 0 0 0 1 2 3 4 5 6
**注:这里第一行为
b
b
b 数组,第二行为 下标
,由于 LaTeX
我用的还不是很熟练,所以还不能很好的控制对齐和缩进,加上标识这个就不对齐了,效果不太好,所以就省略了~大家看到这条之后委屈一下hh,我会尽量说明白的。**
现在开始模拟这一过程:
- 第一次循环,
insert(1, 1, a[1])
,对
[
1
,
1
]
[1, 1]
[1,1] 进行
a
[
1
]
a[1]
a[1] 元素的插入,插入之后结果:
a
[
1
]
−
a
[
1
]
0
0
0
0
1
2
3
4
[外链图片转存中…(img-Jl2GY1Wa-1714737281085)]
[外链图片转存中…(img-EBGmVBYx-1714737281086)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!