set
を上手に使う系の問題
問題概要
のグリッドがあり、最初は各マスに壁がある。このグリッドに次の 回のクエリを実行したあとの、残っている壁の個数を求めよ。
【クエリ】
マス のある位置に爆弾を落とす。
- もしこのマスに壁があるなら、その壁を破壊する
- 壁がないならば、そのマスから上下左右それぞれの方向について、直近の壁を破壊する
制約
考えたこと
たとえば壁のないマス に落とした爆弾の左右方向の壁について考えよう。このとき、
- 右側:行 に残っている壁のある列番号のうち、 以上の最小のものを求め、それを削除する
- 左側:上記の列番号の前の列番号について削除する
という処理を実行することになる。上下方向も同様である。ここで分かるのは「lower_bound()
」と「削除」の操作が必要となることである。このような処理は set
を用いるに限る。
左右方向と上下方向のクエリに対応するために、次のようなデータ構造を用意しておこう。
rows[r]
← 行 に残っている壁のある列番号の集合cols[c]
← 列 に残っている壁のある行番号の集合
このデータ構造を初期化するのには の計算量を要する。制約をよくみると ではなく なので、大丈夫だ。
あとは、毎回のクエリに対して、上記のデータ構造を着実に更新していけばよい。具体的な処理は次のコードを参照。
全体の計算量は となる。
コード
#include <bits/stdc++.h> using namespace std; int main() { int H, W, Q; cin >> H >> W >> Q; vector<set<int>> rows(H, {-1, W}), cols(W, {-1, H}); for (int i = 0; i < H; i++) for (int j = 0; j < W; j++) { rows[i].insert(j); cols[j].insert(i); } auto del = [&](int r, int c) -> void { if (r < 0 || r >= H || c < 0 || c >= W) return; rows[r].erase(c); cols[c].erase(r); }; for (int _ = 0; _ < Q; _++) { int r, c; cin >> r >> c; --r, --c; // (r, c) に壁があるとき auto right = rows[r].lower_bound(c); if (*right == c) { del(r, *right); continue; } // (r, c) に壁がないとき auto left = prev(right); auto down = cols[c].lower_bound(r); auto top = prev(down); del(r, *right), del(r, *left), del(*down, c), del(*top, c); } long long res = 0; for (int r = 0; r < H; r++) res += (long long)rows[r].size() - 2; cout << res << endl; }