Official

G - Cross Explosion Editorial by Nyaan


まず、この問題は問題文の指示に従ってクエリを処理すれば正答を返すことが出来る問題です。しかし、残っている壁の管理に配列のみを用いると時間計算量が \(\mathrm{O}(H^2 W^2)\) あるいは \(\mathrm{O}(HW (H+W))\) かかってしまうため実行時間制限に間に合いません。そのため、アルゴリズムの実装に適切なデータ構造を用いることが要求されています。

残っている壁を順序付き集合 (C++ の std::set) を用いて管理することを考えます。具体的には以下の \(g_1, g_2\) を管理することにします。

  • \(g_1\) : \(i\) 番目の要素に「\(i\) 行目に残っている壁の位置を列番号で持った順序付き集合」を持った配列
  • \(g_2\): \(j\) 番目の要素に「\(j\) 列目に残っている壁の位置を行番号で持った順序付き集合」を持った配列

このようにデータ構造を構成して、順序付き集合上の二分探索(C++ の set::lower_bound 関数) を適宜利用することで、クエリを \(\mathrm{O}(\log H + \log W)\) で処理することが出来るようになります。(詳細は実装例を参考にしてください)
よってこの問題を時間計算量 \(\mathrm{O}((HW + Q) (\log H + \log W))\) で解くことが出来て、これは実行時間制限に十分間に合います。

C++ による実装例は次の通りです。また、tatyam 氏の SortedSet を使用した Python の実装例は こちら です。

#include <cassert>
#include <iostream>
#include <set>
#include <vector>
using namespace std;

int main() {
  cin.tie(nullptr);
  ios::sync_with_stdio(false);

  int H, W, Q;
  cin >> H >> W >> Q;

  vector<set<int>> g1(H), g2(W);
  for (int i = 0; i < H; i++) {
    for (int j = 0; j < W; j++) {
      g1[i].insert(j);
      g2[j].insert(i);
    }
  }

  auto erase = [&](int i, int j) { g1[i].erase(j), g2[j].erase(i); };

  while (Q--) {
    int R, C;
    cin >> R >> C;
    --R, --C;

    if (g1[R].count(C)) {
      erase(R, C);
      continue;
    }

    // 上
    {
      auto it = g2[C].lower_bound(R);
      if (it != begin(g2[C])) erase(*prev(it), C);
    }
    // 下
    {
      auto it = g2[C].lower_bound(R);
      if (it != end(g2[C])) erase(*it, C);
    }
    // 左
    {
      auto it = g1[R].lower_bound(C);
      if (it != begin(g1[R])) erase(R, *prev(it));
    }
    // 右
    {
      auto it = g1[R].lower_bound(C);
      if (it != end(g1[R])) erase(R, *it);
    }
  }

  int ans = 0;
  for (int i = 0; i < H; i++) ans += g1[i].size();
  cout << ans << "\n";
}

posted:
last update: