Official

D - Polyomino Editorial by Nyaan


この問題は上手な実装方針を選べるかによって実装量が大きく変わってくる問題だと思います。上手く実装できなかった人は、この解説や他の人の提出コードを読んでよい方針を探してみましょう。

この問題は制約が小さいので、なんらかの高度なアルゴリズム等を利用せずに単純にあり得る配置を全探索すれば解くことが出来ます。具体的には、以下のようにすべての要素を 1 個 1 個決め打っていけばよいです。

  • \(1\) 番目のポリオミノの向きを固定して、\(2\) 番目, \(3\) 番目のポリオミノの向き \(4 \times 4 = 16\) 通りを全探索する。
    • \(1\) 番目のポリオミノをどこに置くかを全探索する。
      • \(2\) 番目のポリオミノをどこに置くかを全探索する。
        • \(3\) 番目のポリオミノをどこに置くかを全探索する。
  • 条件を満たす配置が見つかれば Yes を出力する。そうでない場合、No を出力する。

このアルゴリズムを適切に実装すればこの問題を解くことが出来ます。
アルゴリズムの手順の中でおそらく一番難しいのはポリオミノを回転した状態を全列挙する部分だと思うのでその部分を詳しく説明します。( ポリオミノを配置する方は ABC307 の C 問題 などの類題もあり比較的慣れている人も多いと考えています。)

この問題ではポリオミノの回転が認められているため、それぞれのポリオミノを回転させて全探索する必要があります。グリッドが正方形の形をしているため \(1\) 番目のポリオミノは回転させなくてよいとしても問題なく、\(1\) 番目の回転の分は省略できますが、それでも \(2\) 番目と \(3\) 番目のポリオミノの向き \(4 \times 4 = 16\) 通りを調べる必要があります。
この部分は方針によっては扱う変数の量がかなり増えて面倒な実装になりそうですが、このような問題で典型的な実装方法を紹介します。

まず、ポリオミノを回転させる部分を関数化しておきましょう。

#define rep(i, n) for (int i = 0; i < (n); i++)

// ポリオミノを右に 90 度回転
void Rotate(vector<string>& v) {
  vector<string> w = v;
  rep(i, 4) rep(j, 4) w[3 - j][i] = v[i][j];
  v = w;
}

そして、次のような二重ループを書きます。

// ps[0], ps[1], ps[2] にポリオミノを格納しているとする
rep(t, 4) {
  rep(u, 4) {
    // check : 今の向きで固定したときに配置可能か調べる関数とする
    check();
    // ps[2] を 90 度回す
    Rotate(ps[2]);
  }
  // ps[1] を 90 度回す
  Rotate(ps[1]);
}

実は、この二重ループによって \(4 \times 4 = 16\) 通り全てを調べることができています。具体的には、\(2\) 番目のポリオミノを\(a\) 回, \(3\) 番目のポリオミノを \(b\) 回右に \(90\) 度回した状態は、for 文のループ変数が \(t = a, u = b\) であるタイミングで調べられています。
このように変数をクルクル回しながら全探索する手法は、テクニカルですが応用範囲の広い実装方法なので (特に列の reverse が絡む実装で頻出) ぜひ覚えてみてください。

問題全体の実装例は以下の通りです。

  • 実装例(C++)
#include <iostream>
#include <string>
#include <vector>
using namespace std;

#define rep(i, n) for (int i = 0; i < (n); i++)

// s を出力してプログラムを終了
void Print(const string& s) {
  cout << s << "\n";
  exit(0);
}

// 右に 90 度回転
void Rotate(vector<string>& v) {
  vector<string> w = v;
  rep(i, 4) rep(j, 4) w[3 - j][i] = v[i][j];
  v = w;
}

// (i, j) がグリッド内部かを判定する
bool in(int i, int j) { return 0 <= i and i < 4 and 0 <= j and j < 4; }

// p を (di, dj) を左上の位置として配置できるか?
bool can_put(vector<vector<int>>& exist, const vector<string>& p, int di,
             int dj) {
  rep(i, 4) rep(j, 4) {
    if (p[i][j] == '#') {
      int ni = i + di;
      int nj = j + dj;
      if (!in(ni, nj)) return false;
      if (exist[ni][nj] == 1) return false;
      exist[ni][nj] = 1;
    }
  }
  return true;
}

// ポリオミノを再帰で置いていく関数
void dfs(int i, const vector<vector<int>>& exist,
         const vector<vector<string>>& ps) {
  if (i == 3) {
    int ok = 1;
    rep(u, 4) rep(v, 4) ok &= exist[u][v];
    if (ok) Print("Yes");
    return;
  }
  for (int di = -3; di <= 3; di++) {
    for (int dj = -3; dj <= 3; dj++) {
      auto ex2 = exist;
      bool flag = can_put(ex2, ps[i], di, dj);
      if (flag) dfs(i + 1, ex2, ps);
    }
  }
}

int main() {
  vector<vector<string>> ps(3);
  rep(i, 3) {
    ps[i].resize(4);
    for (auto& s : ps[i]) cin >> s;
  }
  rep(t, 4) {
    rep(u, 4) {
      dfs(0, vector(4, vector(4, 0)), ps);
      Rotate(ps[2]);
    }
    Rotate(ps[1]);
  }
  Print("No");
}

posted:
last update: