二次元累積和

二次元累積和とは

f:id:greentomatotan:20200903162851p:plain
このような配列を考える。これを配列Aとする。

配列Aの中の任意の長方形の区間の総和を求めたいとする。

f:id:greentomatotan:20200903163230p:plain
図の水色のマスの合計は、1+5+2+6+3+9+3+8=37である。
これは、A[1][2]からA[2][5]までの総和といえる。
これをΣ([1][2],[2][5])と書くことにする。
次の関係式が成り立つ。
Σ([1][2],[2][5])=Σ([0][0],[2][5])-Σ([0][0],[0][5])-Σ([0][0],[2][1])+Σ([0][0],[0][1])

一般化すると次のようになる。
Σ([a][b],[c][d])=Σ([0][0],[c][d])-Σ([0][0],[a-1][d])-Σ([0][0],[c][b-1])+Σ([0][0],[a-1][b-1])

Σ([0][0],[i][j])=S[i][j]とする。(これが二次元累積和)
そうすると
Σ([a][b],[c][d])=S[c][d]-S[a-1][d]-S[c][b-1]+S[a-1][b-1]
となり、Sを前計算で作っておけばO(1)で長方形の区間が求まる。

二次元累積和の作り方

S[i][j]=S[i-1][j]+S[i][j-1]-S[i-1][j-1]+A[i][j]
という漸化式が成り立つ。(図を見ながら確認すればわかる。)
よってインデックスが小さいほうから埋めていけばO(HW)で作成できる。
i=0またはj=0の時は配列外参照になるため別の処理が必要である。
(i)i=0かつj=0の時
S[i][j]=A[i][j]
(ii)i=0かつj≠0の時
S[i][j]=S[i][j-1]+A[i][j]
(iii)i≠0かつj=0の時
S[i][j]=S[i-1][j]+A[i][j]

コード例

図の水色の部分の総和を求める。(答えは37)

#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<math.h>
#include<iomanip>
#include<set>
#include<numeric>
#include<cstring>
#include<cstdio>
#include<functional>
#include<bitset>
#include<limits.h>
#include<cassert>
#include<iterator>
#include<complex>
#include<stack>
#include<sstream>
#include<iterator>
#include<list>

using namespace std;

#define INF LLONG_MAX / 5
#define int long long
#define rep(i, n) for (int i = 0; i < n; i++)
#define sort(v) sort((v).begin(), (v).end())
#define reverse(v) reverse((v).begin(), (v).end())
#define upper(v,hoge) upper_bound(v.begin(),v.end(),hoge)
#define lower(v,hoge) lower_bound(v.begin(),v.end(),hoge)
#define enld endl

signed main() {
	vector<vector<int>>A;
	A = {
		{1,5,8,4,7,4,2},
		{4,7,1,5,2,6,4},
		{8,2,3,9,3,8,1},
		{3,5,6,1,7,9,2}
	};
	int H = 4, W = 7;

	//二次元累積和の作成
	vector<vector<int>>S(H, vector<int>(W));
	rep(i, H) {
		rep(j, W) {
			if (i == 0 && j == 0) {
				S[i][j] = A[i][j];
			}
			else if (i == 0 && j != 0) {
				S[i][j] = S[i][j - 1] + A[i][j];
			}
			else if (i != 0 && j == 0) {
				S[i][j] = S[i - 1][j] + A[i][j];
			}
			else {
				S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + A[i][j];
			}
		}
	}

	int a = 1, b = 2, c = 2, d = 5;
	//Σ([a][b],[c][d])を出力
	//Σ([a][b],[c][d])=S[c][d]-S[a-1][d]-S[c][d-1]+S[a-1][b-1]
	cout << S[c][d] - S[a - 1][d] - S[c][b - 1] + S[a - 1][b - 1];
}

37が出力された。

コーナーケース

Σ([a][b],[c][d])=S[c][d]-S[a-1][d]-S[c][b-1]+S[a-1][b-1]
なのでa=0またはb=0の時は配列外参照になる。
この時は特別な処理が必要である。
(i)a=0かつb=0の時
Σ([a][b],[c][d])=S[c][d]
(ii)a=0かつb≠0の時
Σ([a][b],[c][d])=S[c][d]-S[c][b-1]
(iii)a≠0かつb=0の時
Σ([a][b],[c][d])=S[c][d]-S[a-1][d]

となる。

問題

1行目にH Wが空白区切りで与えられる。
2行目以降に二次元配列が与えられる。
次に整数Qが与えられる。
その次のQ行でa b c dが与えられる。(0-index)
それぞれについてΣ([a][b],[c][d])を求めよ。

入力
H W
A[0][0] ・・・ A[0][W-1]



A[H-1][0] ・・・ A[H-1][W-1]
Q
a_1 b_1 c_1 d_1



a_Q b_Q c_Q d_Q

入力例
4 7
1 5 8 4 7 4 2
4 7 1 5 2 6 4
8 2 3 9 3 8 1
3 5 6 1 7 9 2
5
1 2 2 5
1 6 1 6
0 0 0 3
0 1 1 2
3 0 3 5

出力例
37
4
18
21
31

正解コード

#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#include<math.h>
#include<iomanip>
#include<set>
#include<numeric>
#include<cstring>
#include<cstdio>
#include<functional>
#include<bitset>
#include<limits.h>
#include<cassert>
#include<iterator>
#include<complex>
#include<stack>
#include<sstream>
#include<iterator>
#include<list>

using namespace std;

#define INF LLONG_MAX / 5
#define int long long
#define rep(i, n) for (int i = 0; i < n; i++)
#define sort(v) sort((v).begin(), (v).end())
#define reverse(v) reverse((v).begin(), (v).end())
#define upper(v,hoge) upper_bound(v.begin(),v.end(),hoge)
#define lower(v,hoge) lower_bound(v.begin(),v.end(),hoge)
#define enld endl

signed main() {
	int H, W;
	cin >> H >> W;
	vector<vector<int>>A(H, vector<int>(W));
	rep(i, H) {
		rep(j, W) {
			cin >> A[i][j];
		}
	}
	//二次元累積和の作成
	vector<vector<int>>S(H, vector<int>(W));
	rep(i, H) {
		rep(j, W) {
			if (i == 0 && j == 0) {
				S[i][j] = A[i][j];
			}
			else if (i == 0 && j != 0) {
				S[i][j] = S[i][j - 1] + A[i][j];
			}
			else if (i != 0 && j == 0) {
				S[i][j] = S[i - 1][j] + A[i][j];
			}
			else {
				S[i][j] = S[i - 1][j] + S[i][j - 1] - S[i - 1][j - 1] + A[i][j];
			}
		}
	}
	int Q;
	cin >> Q;
	int a, b, c, d;
	rep(_, Q) {
		cin >> a >> b >> c >> d;
		if (a == 0 && b == 0) {
			cout << S[c][d] << endl;
		}
		else if (a == 0 && b != 0) {
			cout << S[c][d] - S[c][b - 1] << endl;
		}
		else if (a != 0 && b == 0) {
			cout << S[c][d] - S[a - 1][d] << endl;
		}
		else {
			cout << S[c][d] - S[a - 1][d] - S[c][b - 1] + S[a - 1][b - 1] << endl;
		}
	}
}