C - 都市計画と道路整備 / Urban Planning and Road Development 解説 by admin
Gemini 3.0 Flash (Thinking)概要
\(H \times W\) のグリッド上で、道路工事(長方形領域の空き地を道路に変更)を \(Q\) 回行い、各工事の直後に「道路に面している建物の総数」を求める問題です。
考察
1. 道路に面する条件の変化
建物が「道路に面している」状態になるのは、以下のいずれかの場合です。 - 初期状態で、隣接するマスに道路がある。 - 道路工事によって、隣接するマスが空き地から道路に変わる。
一度道路に面した建物は、その後の工事で道路が増えても「道路に面している」状態のままです。したがって、「新しく道路に面することになった建物」を順次カウントしていくというアプローチが有効です。
2. 工事の影響範囲
各工事では、長方形領域 \([U, D] \times [L, R]\) 内の空き地がすべて道路になります。このとき、新しく道路に面する可能性がある建物は、この長方形領域のすぐ外側(上下左右)に隣接している建物だけです。
具体的には、以下の 4 つの範囲にある建物を調べれば十分です。 1. 上端の 1 行上:行 \(U-1\)、列 \(L \sim R\) 2. 下端の 1 行下:行 \(D+1\)、列 \(L \sim R\) 3. 左端の 1 列左:列 \(L-1\)、行 \(U \sim D\) 4. 右端の 1 列右:列 \(R+1\)、行 \(U \sim D\)
問題文の制約に「工事範囲内に建物は含まれない」とあるため、長方形の内部を気にする必要はありません。
3. 計算量の見積もり
素朴に毎クエリ全マスを走査すると \(O(Q \times HW)\) となり間に合いません。 しかし、上記の通り「長方形の境界」のみをチェックする場合、各クエリでの処理は長方形の周囲の長さに比例します。 制約を見ると、\(\sum (\text{周囲の長さ}) \leq 5 \times 10^6\) となっており、この計算量なら十分に間に合うことがわかります。
アルゴリズム
- 初期状態のカウント:
- 全マスを走査し、建物 ‘B’ を見つけたら上下左右に ‘R’ があるか確認します。
- 道路に面していれば、フラグ
is_facing[i][j]を真にし、現在のカウントcurrent_countを増やします。
- クエリ処理:
- 各工事の長方形領域の 4 辺の外側を走査します。
- マス \((i, j)\) が建物 ‘B’ であり、かつ
is_facing[i][j]が偽である場合:is_facing[i][j]を真に更新し、current_countを 1 増やします。
- 各クエリの最後に
current_countを出力します。
計算量
- 時間計算量: \(O(HW + \sum_{k=1}^{Q} (\text{Perimeter}_k))\)
- 初期走査に \(O(HW)\)、各クエリの境界走査に \(O(D_k - U_k + R_k - L_k)\) かかります。制約より、これらは合計で約 \(7 \times 10^6\) 程度の操作回数となり、制限時間内に収まります。
- 空間計算量: \(O(HW)\)
- グリッドの情報を保持する \(S\) と、建物ごとの状態を保持する
is_facing配列(または vector)に \(O(HW)\) のメモリを使用します。
- グリッドの情報を保持する \(S\) と、建物ごとの状態を保持する
実装のポイント
高速な入出力: \(Q\) や \(HW\) が大きいため、C++ の場合は
cin.tie(NULL); ios_base::sync_with_stdio(false);を使用して入出力を高速化するのが望ましいです。1次元配列での管理: 2次元配列
is_facing[H][W]を使う代わりに、is_facing[i * W + j]のように1次元配列で管理することで、メモリ効率やアクセス速度を向上させることができます。範囲外アクセスの防止: 長方形の境界をチェックする際、その境界がグリッドの端(行 0 や \(H-1\) など)を越えないように
if文で境界チェックを行う必要があります。ソースコード
#include <iostream>
#include <vector>
#include <string>
using namespace std;
/**
* Problem: City Planning and Road Maintenance
* We need to count how many buildings 'B' are facing at least one road 'R'.
* Initially, some buildings might already face roads.
* Q road construction queries change all 'E' cells in a rectangle [U, D] x [L, R] to 'R'.
* Buildings are never in the construction region.
* We need to output the count of buildings facing a road after each query.
*
* Logic:
* 1. Identify which buildings initially face a road by checking their 4 neighbors.
* 2. For each query, all cells in the rectangle [U, D] x [L, R] become 'R'.
* 3. Any building outside the region that is adjacent to any cell inside the region will now face a road.
* 4. The buildings adjacent to the rectangle are on its four immediate boundaries:
* - Row U-1, columns L to R
* - Row D+1, columns L to R
* - Column L-1, rows U to D
* - Column R+1, rows U to D
* 5. Update the facing status of these buildings and the total count if they weren't already facing a road.
* 6. Time complexity: O(HW + Sum of Perimeters), which fits within the limits.
* Initial check: O(HW), Query processing: O(Sum of (2*(D-U+1) + 2*(R-L+1))).
* Given HW <= 2e6 and Sum of Perimeters <= 5e6, total operations are ~7e6.
*/
int main() {
// Fast I/O for performance
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int H, W, Q;
if (!(cin >> H >> W >> Q)) return 0;
// Grid storage: use a vector of strings. HW <= 2e6, so this is memory-efficient.
vector<string> S(H);
for (int i = 0; i < H; ++i) {
cin >> S[i];
}
// is_facing[i*W + j] tracks if building (i, j) is facing a road.
// Using a 1D vector to handle the grid. vector<char> uses 1 byte per element.
vector<char> is_facing((long long)H * W, 0);
long long current_count = 0;
// Initial check for buildings already facing a road in the initial grid.
for (int i = 0; i < H; ++i) {
for (int j = 0; j < W; ++j) {
if (S[i][j] == 'B') {
bool facing = false;
// Check 4 cardinal neighbors (Up, Down, Left, Right)
if (i > 0 && S[i - 1][j] == 'R') facing = true;
else if (i < H - 1 && S[i + 1][j] == 'R') facing = true;
else if (j > 0 && S[i][j - 1] == 'R') facing = true;
else if (j < W - 1 && S[i][j + 1] == 'R') facing = true;
if (facing) {
is_facing[(long long)i * W + j] = 1;
current_count++;
}
}
}
}
// Process Q road construction queries.
for (int k = 0; k < Q; ++k) {
int U, D, L, R;
cin >> U >> D >> L >> R;
// Convert 1-based coordinates to 0-based.
--U; --D; --L; --R;
// Any building adjacent to the query rectangle [U, D] x [L, R]
// will now face a road. We check the four potential boundary rows/cols.
// 1. Buildings above the region: row U-1, columns L to R.
if (U > 0) {
int i = U - 1;
for (int j = L; j <= R; ++j) {
if (S[i][j] == 'B' && !is_facing[(long long)i * W + j]) {
is_facing[(long long)i * W + j] = 1;
current_count++;
}
}
}
// 2. Buildings below the region: row D+1, columns L to R.
if (D < H - 1) {
int i = D + 1;
for (int j = L; j <= R; ++j) {
if (S[i][j] == 'B' && !is_facing[(long long)i * W + j]) {
is_facing[(long long)i * W + j] = 1;
current_count++;
}
}
}
// 3. Buildings to the left of the region: column L-1, rows U to D.
if (L > 0) {
int j = L - 1;
for (int i = U; i <= D; ++i) {
if (S[i][j] == 'B' && !is_facing[(long long)i * W + j]) {
is_facing[(long long)i * W + j] = 1;
current_count++;
}
}
}
// 4. Buildings to the right of the region: column R+1, rows U to D.
if (R < W - 1) {
int j = R + 1;
for (int i = U; i <= D; ++i) {
if (S[i][j] == 'B' && !is_facing[(long long)i * W + j]) {
is_facing[(long long)i * W + j] = 1;
current_count++;
}
}
}
// Output the updated count of buildings facing a road after this query.
cout << current_count << "\n";
}
return 0;
}
この解説は gemini-3-flash-thinking によって生成されました。