累積和

累積和

数列の部分和、およびその和を扱うデータ構造を指す。
累積和を利用すると、区間に対するクエリを \(\mathcal{O}(1)\) で処理できることがある。
以下、扱う配列は0-indexedとする。
配列 \(A = a_0, a_1, \ldots, a_{n-1}\) の累積和は、区間 \([0, n)\) の 部分和
\[s_i = \sum_{i=0}^{n-1} a_i\]

を並べた配列 \(S = s_0, s_1, \ldots, s_{n-1}\) として表現される。

実装

Pythonでは、リストやNumpy配列の累積和を求める関数が実装されている。

[1]:
from itertools import accumulate

A = [3, 5, -1, 6, -5, 4]
S = list(accumulate(A))
print(S)
[3, 8, 7, 13, 8, 12]
[2]:
import numpy

S = numpy.cumsum(A)
print(S)
[ 3  8  7 13  8 12]

和に限らず、条件

  • 結合法則が成り立つ \((a \cdot b) \cdot c = a \cdot (b \cdot c)\)

  • 単位元 \(e\) をもつ \(a \cdot e = e \cdot a = a\)

を満たす二項演算であれば累積を定義できる。

累積ORの実装例を示す。

[3]:
from itertools import accumulate
import operator

A = [0b010, 0b011, 0b010, 0b010, 0b101]
S = list(accumulate(A, operator.or_))
print(['{:03b}'.format(s) for s in S])
['010', '011', '011', '011', '111']

部分和

部分列 \([0, k)\) の和は、累積和の \(k\) 番目の要素そのものである。
部分列 \([k, n)\) の和は、数列の逆順に対して累積和をとることで求められる。
[4]:
from itertools import accumulate

A = [3, 5, -1, 6, -5, 4]
S = list(accumulate(A[::-1]))[::-1]
print(S)
[12, 9, 4, 5, -1, 4]

一部の演算については、任意の連続区間に対する累積を扱うことができる。

\([l, r)\) で表される区間 \((l < r)\) の部分和は、累積和 \(S\) を用いて次のように表される。

\[\begin{split}\sum_{i=l}^{r-1} a_i = \left\{ \begin{array}{ll} s_r & (l = 0) \\ s_{r-1} - s_{l-1} & (otherwise) \\ \end{array} \right .\end{split}\]

部分和以外でも、\(s_r, s_l\) に対する逆演算 \(s_r \circ s_l\) が定義されていれば部分累積を \(\mathcal{O}(1)\) で求めることができる。乗算やXORが該当する。

[5]:
from itertools import accumulate

A = [3, 5, -1, 6, -5, 4]
S = list(accumulate(A))
l = 2
r = 4
print(S[r-1] - S[l-1] if l > 0 else S[r-1])
5