C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で行列(2次元配列)の累積和(プレフィックスサム)を計算する方法

この記事では、整数値を格納した2次元配列 mat[][] が与えられたとき、その累積和行列(プレフィックスサム行列)を求める問題について解説します。

累積和行列とは?

累積和行列とは、行列の各要素が「その位置より上側と左側にあるすべての要素の合計」を表す行列のことです。数式で表すと以下のようになります。

prefixSum[i][j] = mat[i][j] + mat[i-1][j] + ... + mat[0][j] + mat[i][j-1] + ... + mat[i][0]

つまり、(i, j) の値は、左上の要素 (0, 0) から (i, j) までの長方形領域内の全要素の総和に等しくなります。

具体例

入力: arr = [
    [4   6   1]
    [5   7   2]
    [3   8   9]
]
出力: [
    [4    10   11]
    [9    22   25]
    [12   33   45]
]

例えば出力の 22 は、入力行列の左上からその位置までの範囲(4 + 6 + 5 + 7)の合計になっています。

解き方:単純なアプローチと効率的なアプローチ

最も単純な方法は、各位置 (i, j) に対して、そこまでのすべての要素を毎回走査して合計を求めることです。しかし、この方法では計算量が O(R² × C²) となり、行列サイズが大きくなると非効率です。

そこで有効なのが、すでに計算済みの累積和の値を再利用する漸化式を使う方法です。これにより O(R × C) の計算量で求められます。

一般式

prefixSum[i][j] = prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1] + a[i][j]

ポイントは「上のセル」と「左のセル」の累積和を足し合わせると、左上のセルが二重にカウントされてしまうため、それを1回引くという点です。

特別なケース(端の処理)

i = 0 かつ j = 0 の場合:
prefixSum[i][j] = a[i][j]

i = 0 かつ j > 0 の場合(1行目):
prefixSum[i][j] = prefixSum[i][j-1] + a[i][j]

i > 0 かつ j = 0 の場合(1列目):
prefixSum[i][j] = prefixSum[i-1][j] + a[i][j]

C++での実装例

以下のコードは、上記のアルゴリズムを実装したものです。

#include <iostream>
using namespace std;
#define R 3
#define C 3

void printPrefixSum(int a[][C]) {
    int prefixSum[R][C];
    // 左上の要素
    prefixSum[0][0] = a[0][0];
    // 1行目の処理
    for (int i = 1; i < C; i++)
        prefixSum[0][i] = prefixSum[0][i - 1] + a[0][i];
    // 1列目の処理
    for (int i = 1; i < R; i++)
        prefixSum[i][0] = prefixSum[i - 1][0] + a[i][0];
    // 残りの要素を一般式で計算
    for (int i = 1; i < R; i++) {
        for (int j = 1; j < C; j++)
            prefixSum[i][j] = prefixSum[i - 1][j] + prefixSum[i][j - 1]
                            - prefixSum[i - 1][j - 1] + a[i][j];
    }
    // 結果の出力
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < C; j++)
            cout << prefixSum[i][j] << "\t";
        cout << endl;
    }
}

int main() {
    int mat[R][C] = {
        { 1, 2, 3},
        { 4, 5, 6},
        { 7, 8, 9}
    };
    cout << "The prefix Sum Matrix is :\n";
    printPrefixSum(mat);
    return 0;
}

実行結果

The prefix Sum Matrix is :
1    3    6
5    12   21
12   27   45

まとめ

累積和行列は、動的計画法の考え方を応用した基本的かつ重要なテクニックです。各要素の計算に直前の結果を再利用することで、計算量を大幅に削減できます。この手法は、部分行列の合計を高速に求めたい場面(競技プログラミングや画像処理など)で広く活用されています。

  1. C++で解く合計配列パズル|自身を除いた要素の総和を効率的に求める方法

    配列(Array)とは 配列とは、同じデータ型の複数の要素をまとめて格納できるデータ構造です。複数の値を一度に扱えるのが大きな特徴ですが、その長さはあらかじめ定義しておく必要があります。 合計配列パズルとは このパズルでは、サイズ n の配列 A1 が与えられます。これを解くために、配列 S1 を作成します。S1 には、対応する位置の要素を除いた A1 の全要素の合計を格納します。たとえば S1[3] を計算する場合、A1 の 4 番目の要素(インデックス 3)以外のすべての要素の合計を求めることになります。 具体例 配列 A1 = {1, 2, 3, 4, 6} 出力 S1 = {15, 1

  2. C++の配列パズル:減算演算子を使わずに「自分以外の要素の合計」を求める方法

    今回は、配列に関する興味深い問題を紹介します。n個の要素を持つ配列が与えられ、それをもとに同じくn個の要素を持つ別の配列を作成します。ただし、新しい配列のi番目には、元の配列のi番目の要素を除いたすべての要素の合計を格納します。さらに重要な制約として、減算演算子(-)を使用してはいけないという条件が課されています。 問題のポイント もし減算が使えるのであれば、話は簡単です。まず全要素の合計を求めておき、そこからi番目の要素を引いた値を新しい配列のi番目に格納すればよいだけです。しかし、この問題では減算が禁止されているため、別のアプローチが必要になります。 そこで、各位置i(0〜n-1)について