2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

《アルゴリズムを学ぶ》13.累積和

Posted at

Overview

累積和(Prefix Sum) は、配列の部分和を高速に求めるためのテクニック。
1.累積和の基本
2.部分和を求める
3.累積和の応用
4.典型問題を解いてみる

1. 累積和の基本

整数$A_1, A_2, ..., A_N$に対し、累積和$B_i = A_1 + A_2 + ... + A_i$を考える、というときに使えるテクニック。
例 : $[3,1,4,1,5,9]$の累積和は$[3,4,8,9,14,23]$

直接計算した場合の計算量は$O(N^2)$になるが、以下のようなロジックにすると$O(N)$になる。

累積和の構築

def prefix_sum(arr):
    N = len(arr)
    S = [0] * (N + 1)  # 0-indexed のため 1つ多く確保
    for i in range(N):
        S[i + 1] = S[i] + arr[i]
    return S

実行イメージ

arr = [3, 1, 4, 1, 5, 9, 2]
print(prefix_sum(arr)) #出力 [0, 3, 4, 8, 9, 14, 23, 25]

実行イメージの累積和 S の計算:

i arr[i] S[i+1] = S[i] + arr[i] S
- - 初期値 [0]
0 3 S[1] = 0 + 3 [0, 3]
1 1 S[2] = 3 + 1 [0, 3, 4]
2 4 S[3] = 4 + 4 [0, 3, 4, 8]
3 1 S[4] = 8 + 1 [0, 3, 4, 8, 9]
4 5 S[5] = 9 + 5 [0, 3, 4, 8, 9, 14]
5 9 S[6] = 14 + 9 [0, 3, 4, 8, 9, 14, 23]
6 2 S[7] = 23 + 2 [0, 3, 4, 8, 9, 14, 23, 25]

2. 部分和を求める

部分和、つまり区間[l,r]の合計を求めるのは、$S[r+1]−S[l]$。

A = [3, 1, 4, 1, 5, 9, 2]
S = prefix_sum(A) #1.累積和の構築の関数を呼び出す

l, r = 2, 5  # 0-indexed
print(S[r+1] - S[l])  # 出力: 4 + 1 + 5 + 9 = 19

・従来の計算だと $O(N)$ かかるが、累積和なら$ O(1)$
・累積和を事前計算することで高速に処理できる

3. 累積和の応用

(1) 区間の平均を求める

部分和がすぐに求まるので、平均も O(1) で求められる。

def range_average(S, l, r):
    return (S[r+1] - S[l]) / (r - l + 1)

A = [3, 1, 4, 1, 5, 9, 2]
S = prefix_sum(A) #1.累積和の構築の関数を呼び出す

l, r = 2, 5
print(range_average(S, l, r))  # 平均値 (4+1+5+9)/4 = 4.75

(2) 2次元累積和

問題: グリッド N×M の 長方形領域の合計を O(1) で求める。

def build_2d_prefix_sum(grid):
    H, W = len(grid), len(grid[0])
    S = [[0] * (W + 1) for _ in range(H + 1)]
    for i in range(H):
        for j in range(W):
            S[i+1][j+1] = S[i][j+1] + S[i+1][j] - S[i][j] + grid[i][j]
    return S

def query_2d(S, x1, y1, x2, y2):
    return S[x2+1][y2+1] - S[x1][y2+1] - S[x2+1][y1] + S[x1][y1]

grid = [
    [3, 1, 4],
    [1, 5, 9],
    [2, 6, 5]
]
S = build_2d_prefix_sum(grid)

print(query_2d(S, 1, 1, 2, 2))  # 出力: 5 + 9 + 6 + 5 = 25

・2D累積和を使うと、部分領域の合計が$O(1)$
・通常の計算では $O(NM)$ かかるが、高速化可能

4. 典型問題を解いてみる

例題1. 1次元累積和(区間和)

問題: N 個の整数 A と Q 個のクエリ l, r が与えられる。各クエリの区間 [l, r] の和を求めよ。

参考: ABC315 B - Prefix Sum

回答 
N, Q = map(int, input().split())
A = list(map(int, input().split()))
S = prefix_sum(A)

for _ in range(Q):
    l, r = map(int, input().split())
    print(S[r+1] - S[l])

例題2. 累積和を使った平均計算

問題: N 個の整数 A が与えられる。Q 個のクエリに対して区間 [l, r] の平均を求めよ。

参考: ABC318 B - Prefix Sum Average

回答 
N, Q = map(int, input().split())
A = list(map(int, input().split()))
S = prefix_sum(A)

for _ in range(Q):
    l, r = map(int, input().split())
    print((S[r+1] - S[l]) / (r - l + 1))

例題3. 2次元累積和(長方形領域の合計)

問題: H×W のグリッドが与えられる。Q 個のクエリ (x1, y1, x2, y2) に対し、指定領域の合計を求めよ。

参考: ABC319 C - 2D Prefix Sum

回答 
H, W, Q = map(int, input().split())
grid = [list(map(int, input().split())) for _ in range(H)]
S = build_2d_prefix_sum(grid)

for _ in range(Q):
    x1, y1, x2, y2 = map(int, input().split())
    print(query_2d(S, x1, y1, x2, y2))

例題4. 条件付き累積和

問題: N 個の整数 A から、偶数のみの累積和を求め、クエリに答えよ。

参考: ABC320 B - Even Prefix Sum

回答 
N, Q = map(int, input().split())
A = list(map(int, input().split()))
S = [0] * (N+1)

for i in range(N):
    S[i+1] = S[i] + (A[i] if A[i] % 2 == 0 else 0)

for _ in range(Q):
    l, r = map(int, input().split())
    print(S[r+1] - S[l])
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?