累積和
コード例
#include <bits/stdc++.h>
using namespace std;
int main() {
  int n = 5; // vの要素数
  vector<int> v = {3, 1, 5, 8, 2}; // 対象の配列
  vector<int> t(n + 1, 0); // 累積和を格納する配列
  for (int i = 0; i < n; i++) {
    t[i + 1] = t[i] + v[i];
  }
  cout << t[5] - t[0] << endl;  // [0, 5) => 19
  cout << t[5] - t[2] << endl;  // [2, 5) => 15
  cout << t[2] - t[2] << endl;  // [2, 2) => 0
}
解説/アルゴリズム
累積和は、 の前処理を行うことによって、配列上の区間の総和を で求めることができるアルゴリズム。
| 添字 | [0] | [1] | [2] | [3] | [4] | 
|---|---|---|---|---|---|
| 要素 | 3 | 1 | 5 | 8 | 2 | 
まず対象となる配列 v を用意する。
新しい配列 t を用意して下記の計算式で値を代入していく。
- t[0] には 0
- t[1] には v[0]
- t[2] には v[0] + v[1]
- t[3] には v[0] + v[1] + v[2]
- t[n] には v[0] + v[1] + v[2] + ... v[n - 1]
| 添字 | [0] | [1] | [2] | [3] | [4] | [5] | 
|---|---|---|---|---|---|---|
| 計算式 | 0 | [0] | [0]+[1] | [0]+[1]+[2] | [0]+[1]+[2]+[3] | [0]+[1]+[2]+[3]+[4] | 
| 要素 | 0 | 3 | 4 | 9 | 17 | 19 | 
と配列に値を入れていく。
vector<int> v = {3, 1, 5, 8, 2};
vector<int> t(n + 1, 0);
for (int i = 0; i < n; i++) {
  t[i + 1] = t[i] + v[i];
}
この代入処理は上記のように で記述できる。
cout << t[5] - t[0] << endl; // [0, 5) => 19
cout << t[5] - t[2] << endl; // [2, 5) => 15
cout << t[2] - t[2] << endl; // [2, 2) => 0
求めた配列 t を t[b] - t[a] のように使用すると、v[a]~ v[b-1] の値の総和を求めることができる。
| 添字 | [0] | [1] | [2] | [3] | [4] | [5] | 
|---|---|---|---|---|---|---|
| 計算式 | 0 | [0] | [0]+[1] | [0]+[1]+[2] | [0]+[1]+[2]+[3] | [0]+[1]+[2]+[3]+[4] | 
| 要素 | 0 | 3 | 4 | 9 | 17 | 19 | 
たとえば t[5] - t[2] というのは、配列 v の [0]+[1]+[2]+[3]+[4] - [0]+[1] で、 [0]+[1] の部分が消えて [2]+[3]+[4] だけが残るのがわかる。
二次元累積和
#include <bits/stdc++.h>
using namespace std;
int main() {
  int w = 4;
  int h = 4;
  vector<vector<int>> v = {
    {1, 0, 0, 1},
    {1, 1, 0, 0},
    {0, 0, 0, 1},
    {0, 1, 1, 0}
  };
  vector<vector<int>> s(h + 1, vector<int>(w + 1, 0));
  for (int y = 0; y < h; y++) {
    for (int x = 0; x < w; x++) {
      s[y + 1][x + 1] = s[y + 1][x] + s[y][x + 1] - s[y][x] + v[y][x];
    }
  }
  auto f = [&](int x1, int y1, int x2, int y2) -> int {
    return s[y2][x2] - s[y2][x1] - s[y1][x2] + s[y1][x1];
  };
  cout << f(0, 0, w, h) << endl;  // 7
  cout << f(2, 1, w, h) << endl;  // 2
}
二次元累積和は、累積和の考えを二次元に拡張したもので、前処理をしている状態で (x1, y1, x2, y2) を渡すと、v[y1][x1] ~ v[y2 - 1][x2 - 1]までの矩形範囲の総和を で求めることができる。
vector<vector<int>> s(h + 1, vector<int>(w + 1, 0));
一次元の場合と同じ考えで、もとの配列より一つ大きいサイズで配列を作成し、左端と上端が 0 になるように埋めておく。
for (int y = 0; y < h; y++) {
  for (int x = 0; x < w; x++) {
    s[y + 1][x + 1] = s[y + 1][x] + s[y][x + 1] - s[y][x] + v[y][x];
  }
}
左から右へ、上から下へ、値を足していく。 s[y][x]には、上にある s[y-1][x]、左にある s[y][x-1]、もとの配列にある v[y][x]を足し合わせた値を格納するのだが、左上部分が重複しているので、その重複分である s[y-1][x-1]の値分だけ引いておく必要がある。
auto f = [&](int x1, int y1, int x2, int y2) -> int {
  return s[y2][x2] - s[y2][x1] - s[y1][x2] + s[y1][x1];
};
(x1, y1)~(x2, y2) の矩形範囲は、 (0, 0)~(x2, y2) から (0, 0)~(x1, y2) と (0, 0)~(x2, y1) を引いて、重複した (0, 0)~(x1, y1) を足したものになる。