Official

D - Cuboid Sum Query Editorial by MtSaka


この問題はいわゆる累積和 の練習問題です。 数列に対する累積和( \(1\) 次元累積和) を知っていることを前提とします。

\(1\) 次元累積和は以下のような問題を解くときに用います。

長さ \(N\) の数列 \(A\) が与えられます。 以下の \(Q\) 個のクエリを処理してください。

\(i\) 番目のクエリでは \(1 \leq L_i \leq R_i \leq N\) を満たす整数の組 \((L_i,R_i)\) が与えられるので、\(\sum_{k=L_i}^{R_i}A_k\) を求めてください

この問題の解法として、長さ \(N+1\) の数列 \(S=(S_0,S_1,\ldots S_N)\)\(S_0=0,S_i=\sum_{k=1}^{i-1}A_k\) と定義し 、\(\sum_{k=L_i}^{R_i}A_k=S_{R_i+1}-S_{L_i}\) を利用して各クエリを高速に求めるという解法があります。

この解法における \(S\) を一般的に \(A\) の累積和と呼んだりすることがあります。

今回の問題はこの \(1\) 次元累積和の問題を \(3\) 次元に拡張したものになっています。

先ほどの定義と似たように \(1 \leq i,j,k \leq N+1\) に対して\(S_{i,j,k}=\sum_{x=1}^{i-1} \sum_{y=1}^{j-1}\sum_{z=1}^{k-1}A_{x,y,z}\) を満たす\(S\) を考えます。(\(i,j,k\) のいずれかが \(0\) のとき、\(S_{i,j,k}=0\) とします。)

このとき、\(\sum_{x=Lx_i}^{Rx_i} \sum_{y=Ly_i}^{Ry_i} \sum_{z=Lz_i}^{Rz_i} A_{x,y,z}=S_{Rx_i+1,Ry_i+1,Rz_i+1}-S_{Lx_i,Rx_i+1,Rz_i+1}-S_{Rx_i+1,Ly_i,Rz_i+1}-S_{Rx_i+1,Ry_i+1,Lz_i}+S_{Lx_i,Ly_i,Rz_i+1}+S_{Lx_i,Ry_i+1,Lz_i}+S_{Rx_i+1,Ly_i,Lz_i}-S_{Lx_i,Ly_i,Lz_i}\) を満たします。

そのため、\(S\) を高速に求めることができれば、各クエリを定数時間で処理することができます。

実際に、\(S\) を高速に求める際は、\(S_{i,j,k}=S_{i-1,j,k}+S_{i,j-1,k}+S_{i,j,k-1}-S_{i-1,j-1,k}-S_{i-1,j,k-1}-S_{i,j-1,k-1}+S_{i-1,j-1,k-1}+A_{i-1,j-1,k-1}\) を満たすことを利用することで求められます。時間計算量は\(O(N^3+Q)\) になります。

詳細は実装例を参照してください。

実装例(C++):

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector a(n, vector(n, vector(n, 0)));
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int k = 0; k < n; ++k) {
                cin >> a[i][j][k];
            }
        }
    }
    vector sum(n + 1, vector(n + 1, vector(n + 1, 0LL)));
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int k = 0; k < n; ++k) {
                sum[i + 1][j + 1][k + 1] =
                    sum[i][j + 1][k + 1] + sum[i + 1][j][k + 1] +
                    sum[i + 1][j + 1][k] - sum[i][j][k + 1] - sum[i][j + 1][k] -
                    sum[i + 1][j][k] + sum[i][j][k] + a[i][j][k];
            }
        }
    }
    int q;
    cin >> q;
    for (int i = 0; i < q; ++i) {
        int lx, rx, ly, ry, lz, rz;
        cin >> lx >> rx >> ly >> ry >> lz >> rz;
        lx--, ly--, lz--;
        cout << sum[rx][ry][rz] - sum[lx][ry][rz] - sum[rx][ly][rz] -
                    sum[rx][ry][lz] + sum[lx][ly][rz] + sum[lx][ry][lz] +
                    sum[rx][ly][lz] - sum[lx][ly][lz]
             << "\n";
    }
}

実装例(Python):

n = int(input())

a = [[[0] * n for _ in range(n)] for _ in range(n)]
for i in range(n):
    for j in range(n):
        a[i][j]=list(map(int,input().split()))

sum = [[[0] * (n + 1) for _ in range(n + 1)] for _ in range(n + 1)]

for i in range(n):
    for j in range(n):
        for k in range(n):
            sum[i + 1][j + 1][k + 1] = (sum[i][j+1][k+1]
                                        + sum[i+1][j][k+1]
                                        + sum[i+1][j+1][k]
                                        - sum[i][j][k+1]
                                        - sum[i][j+1][k]
                                        - sum[i+1][j][k]
                                        + sum[i][j][k]
                                        + a[i][j][k])

q = int(input())
for _ in range(q):
    lx, rx, ly, ry, lz, rz = map(int, input().split())
    lx -= 1
    ly -= 1
    lz -= 1

    result = (sum[rx][ry][rz]
                - sum[lx][ry][rz]
                - sum[rx][ly][rz]
                - sum[rx][ry][lz]
                + sum[lx][ly][rz]
                + sum[lx][ry][lz]
                + sum[rx][ly][lz]
                - sum[lx][ly][lz])
    print(result)

posted:
last update: