グリッドの縦方向の情報と横方向の情報を前処理するのは典型ですね!!
類題とか
問題概要
のグリッドの各マス目に数値が書かれている。
各マス に対して、「そのマスと行または列が等しいマスの数値の総和」を求めて出力せよ。
制約
考えたこと
各 に対して、総和をとるべきマスの個数は 個になります (下図)。なぜなら
- 同じ行のマスの個数: 個
- 同じ列のマスの個数: 個
となるからです。よって 個だと考えたくなるのですが、マス 自身はどちらのグループにも属するので重複してしまいます。その重複を除いて 個になります。
よって、それらのマスの値を愚直に総和を取るのでは、計算量は の計算量となります。なお、 の の部分は計算量オーダーを考えるときは無視できます。
包除原理
このように 個もの値を計算しないといけない場面では、各値を で計算できないかを考えてみるのが有効でしょう。たとえば , でマス (0-indexed) を考えるとき、下図のように
(行 の総和) + (列 の総和) - (マス の値)
として計算できることがわかります。なおここでマス の値を引くように、「重複しているところを足したり引いたりする」ことを包除原理と呼ぶことがあります。
前処理
上の考察から、次の 個の値を予め求めておけば、各マスの答えを で計算できることがわかります。
- 行 の総和
- 行 の総和
- ...
- 行 の総和
- 列 の総和
- 列 の総和
- ...
- 列 の総和
これらの値をすべて求める作業は で計算できます。このように「ある種の必要な値」を予め求めておくことで、その後の「各マスごとの計算」を高速にできるようにしておくことを、前処理と呼びます。
アルゴリズム全体の計算量を評価すると、
- 前処理の計算量:
- その後すべてのマスについて答えを計算: ( 個のマスについてそれぞれ で計算できるため)
となりますので、全体として となります。
コード (C++)
#include <bits/stdc++.h> using namespace std; int main() { // 入力 int H, W; cin >> H >> W; vector<vector<int>> A(H, vector<int>(W)); for (int i = 0; i < H; ++i) for (int j = 0; j < W; ++j) cin >> A[i][j]; // 前処理 vector<int> yoko(H, 0); // i 行目の総和 vector<int> tate(W, 0); // j 列目の総和 for (int i = 0; i < H; ++i) { for (int j = 0; j < W; ++j) { yoko[i] += A[i][j]; tate[j] += A[i][j]; } } // 各マス for (int i = 0; i < H; ++i) { for (int j = 0; j < W; ++j) { cout << yoko[i] + tate[j] - A[i][j] << " "; } cout << endl; } }
コード (Python3)
# 入力 H, W = map(int, input().split()) A = [list(map(int, input().split())) for _ in range(H)] # 前処理 yoko = list(map(sum, A)) tate = list(map(sum, zip(*A))) # 各マス for i in range(H): print(' '.join(map(lambda j: str(yoko[i] + tate[j] - A[i][j]), range(W)))