2次元差分配列を用いたカーペット問題の解法

問題の説明

n x n のグリッド上に m 枚のカーペットが置かれています。これらのカーペットの情報が与えられたとき、各マスが何枚のカーペットで覆われているかを求めてください。

入力形式

第一行に、2つの正の整数 n, m が与えられます。

続く m 行に、それぞれ 2 つの座標 (x1, y1) と (x2, y2) が与えられます。これは、左上のマスが (x1, y1)、右下のマスが (x2, y2) であるカーペットを表します。

出力形式

n 行の出力をします。各行に n 個の整数を含みます。

i 行目の j 列目の整数は、マス (i, j) が覆われているカーペットの数を表します。

サンプル入力 1

5 3
2 2 3 3
3 3 5 5
1 2 1 4

サンプル出力 1

0 1 1 1 0
0 1 1 0 0
0 1 2 1 1
0 0 1 1 1
0 0 1 1 1

解説

この問題を解くには、各カーペットの範囲内のすべてのマスに 1 を加算する操作を m 回繰り返す必要があります。この操作を愚直に行うと、時間計算量は O(m * n^2) となり、n と m が最大 1000 であるため、時間切れになります。

より効率的な方法として、2次元差分配列 (2D Difference Array) を使用します。この手法では、各カーペットの範囲を O(1) でマークし、最後に全体の累積和を一度計算することで、最終的なグリッドを O(n^2) で構築できます。

具体的なマークの仕方は以下の通りです。カーペットの範囲が (top_left_row, top_left_col) から (bottom_right_row, bottom_right_col) の場合、差分配列 diff に対して以下の操作を行います。

  • diff[top_left_row][top_left_col] += 1
  • diff[top_left_row][bottom_right_col + 1] -= 1
  • diff[bottom_right_row + 1][top_left_col] -= 1
  • diff[bottom_right_row + 1][bottom_right_col + 1] += 1

これらの操作により、指定された矩形領域内のすべてのマスに 1 を加算する効果が得られます。最後に、この差分配列に対して 2 次元累積和を計算することで、各マスが実際に何枚のカーペットで覆われているかを求めることができます。

コード例 1 (差分配列を明示的に使用)

#include <iostream>
#include <cstring>
using namespace std;

const int MAX_SIZE = 1010;

int main() {
    int grid_size, carpet_count;
    cin >> grid_size >> carpet_count;

    // 差分配列を 0 で初期化
    int diff[MAX_SIZE][MAX_SIZE] = {0};

    // m 枚のカーペットの情報を処理
    for (int i = 0; i < carpet_count; ++i) {
        int top_left_row, top_left_col, bottom_right_row, bottom_right_col;
        cin >> top_left_row >> top_left_col >> bottom_right_row >> bottom_right_col;

        // 差分配列にマークを加える
        diff[top_left_row][top_left_col] += 1;
        diff[top_left_row][bottom_right_col + 1] -= 1;
        diff[bottom_right_row + 1][top_left_col] -= 1;
        diff[bottom_right_row + 1][bottom_right_col + 1] += 1;
    }

    // 差分配列から元の配列を復元し、出力する
    for (int i = 1; i <= grid_size; ++i) {
        for (int j = 1; j <= grid_size; ++j) {
            // 2次元累積和を計算
            diff[i][j] = diff[i][j] + diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1];
            cout << diff[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

コード例 2 (元の配列を差分配列として使用)

元の配列がすべて 0 で初期化されている場合、それ自体を差分配列として利用できます。これにより、余分な配列を確保する必要がなくなり、メモリ使用量を節約できます。

#include <iostream>
#include <cstring>
using namespace std;

const int MAX_SIZE = 1010;

int main() {
    int grid_size, carpet_count;
    cin >> grid_size >> carpet_count;

    // 元の配列(差分配列としても使用)を 0 で初期化
    int grid[MAX_SIZE][MAX_SIZE] = {0};

    // m 枚のカーペットの情報を処理
    for (int i = 0; i < carpet_count; ++i) {
        int top_left_row, top_left_col, bottom_right_row, bottom_right_col;
        cin >> top_left_row >> top_left_col >> bottom_right_row >> bottom_right_col;

        // 差分配列にマークを加える
        grid[top_left_row][top_left_col] += 1;
        grid[top_left_row][bottom_right_col + 1] -= 1;
        grid[bottom_right_row + 1][top_left_col] -= 1;
        grid[bottom_right_row + 1][bottom_right_col + 1] += 1;
    }

    // 差分配列から元の配列を復元し、出力する
    for (int i = 1; i <= grid_size; ++i) {
        for (int j = 1; j <= grid_size; ++j) {
            // 2次元累積和を計算
            grid[i][j] = grid[i][j] + grid[i-1][j] + grid[i][j-1] - grid[i-1][j-1];
            cout << grid[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

タグ: C++ 2次元差分配列 累積和 競技プログラミング

5月14日 18:46 投稿