前缀和与差分

897 阅读4分钟

前缀和

1.什么是前缀和

这里只介绍一维前缀和与二维前缀和

即假设有一数列{an}\{ a_n\} ,那么前缀和就可以理解为 a1+a2+a3+...+aia_1 + a_2 + a_3 + ... + a_i。而前缀和数组即为存储以a1a_1为起点,aia_i为终点的前缀和的数组。

举个例子:

b1=a1b_1 = a_1

b2=a1+a2b_2 = a_1 + a_2

b3=a1+a2+a3b_3 = a_1 + a_2 + a_3

......

bn=a1+a2+a3+...+anb_n = a_1 + a_2 + a_3 + ... + a_n

即可得

b1=a1b_1 = a_1

b2=b1+a2b_2 = b_1 + a_2

b3=b2+a3b_3 = b_2+ a_3

......

bn=bn1+anb_n = b_{n-1}+ a_n

{bn}\{b_n\}{an}\{a_n\}的前缀和数组。

2.为什么需要前缀和?前缀和的作用是什么?

前缀和是一种重要的预处理,能大大降低查询的时间复杂度。何以见得?

举个例子:

假设我们需要计算 a1a_1~ana_n的和,通常我们的实现方式是:用一个for循环来求。那如果我需求换了,我要算a7a_7 ~ ana_n的和呢?那还是需要另一个for循环来求,那需求又换了,我要计算a4a_4 ~ an4a_{n-4}呢?如果有n个这样的需求,那我们就要写n个循环,时间复杂度就变高了。

结论:

所以我们通过前缀和,一次for循环把a1a_1 ~ aia_i 的和都算出来存到一个数组b中。

若我们需要求ala_l ~ ara_r 的和,只需要利用b[r]b[l1]b[r] - b[l-1] 即可得到ala_l ~ ara_r的和,这个计算操作的时间复杂度为O(1)O(1)的。

证明:

b[r]=a[1]+a[2]+...+a[l1]+a[l]+a[l+1]+...+a[r]b[r] = a[1] + a[2] + ... + a[l-1] + a[l] + a[l+1] + ... + a[r]

b[l1]=a[1]+a[2]+...+a[l1]b[l-1] = a[1] + a[2] + ... + a[l-1]

所以 b[r]b[l1]=a[l]+...+a[r]b[r] - b[l-1] = a[l] + ... + a[r]

3.二维前缀和

之前讲的都是一维的前缀和,你可能没有感觉到提升了多大的效率,到了二维数组,你可以自己脑补一下不用前缀和处理有多慢了。

二维前缀和相对难理解一点点,重要的是搞清楚前缀和数组存的是什么。

若有二维数组anma_{nm}如下

[a11a12a13a14a21a22a23a24a31a32a33a34a41a42a43a44]\left[ \begin{matrix} a_{11} &a_{12} &a_{13} &a_{14}\\a_{21} &a_{22} &a_{23} &a_{24}\\a_{31} &a_{32} &a_{33} &a_{34}\\a_{41} &a_{42} &a_{43} &a_{44}\end{matrix} \right]

那么anma_{nm}的前缀和数组表示的是什么意思呢?记bnmb_{nm}anma_{nm}的前缀和数组。

b[i][j]b[i][j]表示的是以a[1][1]a[1][1]为左上角,以a[i][j]a[i][j]为右下角的矩形,矩形内所有元素的和。

举个例子

b[2][3]b[2][3]表示的就是下列所有元素的和

[a11a12a13a21a22a23]\left[\begin{matrix}a_{11} &a_{12} &a_{13}\\a_{21} &a_{22} &a_{23} \end{matrix}\right]

类似的

b[4][2]b[4][2]表示的就是下列所有元素的和

[a11a12a21a22a31a32a41a42]\left[\begin{matrix} a_{11} &a_{12} \\a_{21} &a_{22} \\ a_{31} &a_{32}\\ a_{41} &a_{42}\end{matrix}\right]

结论

若我们要求a[x1][y1]a[x_1][y_1]为左上角,a[x2][y2]a[x_2][y_2]为右下角的矩形内所有元素的和,

b[x2][y2]b[x2][y11]b[x11][y2]+b[x11][y11]b[x_2][y_2] - b[x_2][y_1-1] - b[x_1-1][y_2] + b[x_1-1][y_1-1]。

证明就自己画个图就好了,还是比较简单的。记忆起来也比较容易,下标为1的都减一。

差分

1.什么是差分

可以理解为前缀和的逆运算。怎么理解?直接看个例子吧。

设有一差分数组{an}\{a_n\} ,其前缀和数组为{bn}\{b_n\}

则差分数组

a1=b1a_1 = b_1

a2=b2b1a_2 = b_2 - b_1

a3=b3b2a_3 = b_3 - b_2

......

an=bnbn1a_n = b_n - b_{n-1}

而前缀和数组

b1=a1b_1 = a_1

b2=a1+a2b_2 = a_1 + a_2

b3=a1+a2+a3b_3 = a_1 + a_2 + a_3

......

bn=a1+a2+...+anb_n = a_1 + a_2 + ... + a_n

这下就能理解差分了吧。一定要弄明白前缀和再来看差分。

2.差分的作用

用于维护多次对序列的一个区间加上一个数,比如我要在a1a_1~an5a_{n-5} 这一段上每一个元素加上cc,不要再想着用多个循环了噢,类似于前缀和,多次循环过后时间复杂度会变高。

3.差分的使用

对于一维的差分而言,想要在某段区间{l,r}\{l,r\}加上一个数cc,只需要

a[l]+=ca[l] += c

a[r+1]=ca[r+1] -= c

即可。

证明:我们一定要理解,差分数组加上一个数后,对前缀和数组的影响!

假设a1a_1加上一个cc,则前缀和数组{bn}\{b_n\}会变成如下

b1=a1+cb_1 = a_1 + c

b2=a1+c+a2b_2 = a_1 + c + a_2

b3=a1+c+a2+a3b_3 = a_1 + c + a_2 + a_3

......

bn=a1+c+a2+...+anb_n = a_1 + c + a_2 + ... + a_n

可以发现,b1b_1 ~ bnb_n都加上溜了一个cc,那如果此时 a2a_2 减去一个cc呢?

b1=a1+cb_1 = a_1 + c

b2=a1+c+a2c=a1+a2b_2 = a_1 + c + a_2 - c = a_1 + a_2

b3=a1+c+a2c+a3=a1+a2+a3b_3 = a_1 + c + a_2 - c + a_3 = a_1 + a_2 + a_3

......

bn=a1+a2+...+anb_n = a_1 + a_2 + ... + a_n

最后的结果就只有 b1b_1 加了 cc 而已。更多的例子可以由读者自行测试加深印象。

所以,若 aia_i 加了一个数 cc ,那么对应的前缀和数组中 bib_i 以及它后面所有的数都会加上 cc,同理,若aja_j 减去一个数 cc ,那么对应的前缀和数组中 bjb_j 以及它后面所有的数都会减去 cc

4.二维差分

有了一维差分的基础,我们来看看二维差分。首先一定要记住二维前缀和的含义,这个对理解二维差分很关键。

如下,有一差分数组 {a44}\{a_{44}\} ,它对应的前缀和数组为 {b44}\{b_{44}\},即

[a11a12a13a14a21a22a23a24a31a32a33a34a41a42a43a44]\left[ \begin{matrix} a_{11} &a_{12} &a_{13} &a_{14}\\a_{21} &a_{22} &a_{23} &a_{24}\\a_{31} &a_{32} &a_{33} &a_{34}\\a_{41} &a_{42} &a_{43} &a_{44}\end{matrix} \right][b11b12b13b14b21b22b23b24b31b32b33b34b41b42b43b44]\left[ \begin{matrix} b_{11} &b_{12} &b_{13} &b_{14}\\b_{21} &b_{22} &b_{23} &b_{24}\\b_{31} &b_{32} &b_{33} &b_{34}\\b_{41} &b_{42} &b_{43} &b_{44}\end{matrix} \right]

若此时 a22a_{22} 加上一个数 cc ,那么前缀和数组会变成什么样呢?答案如下

[b11b12b13b14b21b22+cb23+cb24+cb31b32+cb33+cb34+cb41b42+cb43+cb44+c]\left[ \begin{matrix} b_{11} &b_{12} &b_{13} &b_{14}\\b_{21} &b_{22} + c &b_{23} + c &b_{24} + c\\b_{31} &b_{32} + c &b_{33} + c &b_{34} + c\\b_{41} &b_{42} + c &b_{43} + c &b_{44} + c\end{matrix} \right]

那继续 a24a_{24} 减去一个数 cc,则

[b11b12b13b14b21b22+cb23+cb24b31b32+cb33+cb34b41b42+cb43+cb44]\left[ \begin{matrix} b_{11} &b_{12} &b_{13} &b_{14}\\b_{21} &b_{22} + c &b_{23} + c &b_{24} \\b_{31} &b_{32} + c &b_{33} + c &b_{34}\\b_{41} &b_{42} + c &b_{43} + c &b_{44}\end{matrix} \right]

继续 a42a_{42} 减去一个数 cc,则

[b11b12b13b14b21b22+cb23+cb24b31b32+cb33+cb34b41b42b43b44c]\left[ \begin{matrix} b_{11} &b_{12} &b_{13} &b_{14}\\b_{21} &b_{22} + c &b_{23} + c &b_{24} \\b_{31} &b_{32} + c &b_{33} + c &b_{34}\\b_{41} &b_{42} &b_{43} &b_{44} - c\end{matrix} \right]

最后 a44a_{44} 加上一个数 cc,即得

[b11b12b13b14b21b22+cb23+cb24b31b32+cb33+cb34b41b42b43b44]\left[ \begin{matrix} b_{11} &b_{12} &b_{13} &b_{14}\\b_{21} &b_{22} + c &b_{23} + c &b_{24} \\b_{31} &b_{32} + c &b_{33} + c &b_{34}\\b_{41} &b_{42} &b_{43} &b_{44}\end{matrix} \right]

而差分数组此时为

[a11a12a13a14a21a22+ca23a24ca31a32a33a34a41a42ca43a44+c]\left[ \begin{matrix} a_{11} &a_{12} &a_{13} &a_{14}\\a_{21} &a_{22} + c &a_{23} &a_{24} - c\\a_{31} &a_{32} &a_{33} &a_{34}\\a_{41} &a_{42} - c &a_{43} &a_{44} + c\end{matrix} \right]

如此我们可以看出,要想给以 ax1y1a_{x_1y_1} 为左上角,以 ax2y2a_{x_2y_2} 为右下角的小矩形中所有元素加上一个数 cc,则需要

a[x1][y1]+=ca[x_1][y_1] += c

a[x1][y2+1]=ca[x_1][y_2+1] -= c

a[x2+1][y1]=ca[x_2+1][y_1] -= c

a[x2+1][y2+1]+=ca[x_2+1][y_2+1] += c

更多Java内容欢迎扫码关注我的公众号ACJavaBear,文章第一时间会发在上面。一起学Java吧