D – Tiling とは?

D – Tiling は「モノグサプログラミングコンテスト2024(AtCoder Beginner Contest 345)」で出題された問題です。管理人がはじめて AtCoder Beginner Contest に参戦したときの記念すべき問題です。初陣の結果は散々なもので 2 問しか解けませんでした。当然 D – Tiling にいたってはどう考えていいかまったくわかりませんでした。

D – Tiling

問題の概要は以下のとおりです。

一辺の長さが 1 のマスからなる H 行 W 列のマス目と、N 枚のタイルがある。
i 枚目のタイルは A[i] × B[i] の長方形である。
使用しないタイルがあっても良いし、タイルを回転させて置いても良い。ただし各タイルはマスの線に合わせてマス目からはみ出ることがないように置かなければならない。
このような条件ですべてのマスがちょうど 1 枚のタイルで覆われている状態にすることができるか判定せよ。

解法ですが、すべてのタイルをどの順場で置くか、タイルを置くときはそのまま、90度回転した場合で全パターン試します。すべてのマスがちょうど 1 枚のタイルで覆われている状態にすることができるものがひとつでもあれば “Yes” が答えです。順列全列挙と bit 全探索を組み合わせたような問題といえます。

しかしもっとうまい方法があります。全部 bit 全探索でやってしまうのです。マスは縦横最大 10(合計 100)なので long 型(64bit)ではできません。128bit 整数型を使います。

先に AC コードを示します。2次元配列を使って全探索するのと比べると 10 倍の速さです。なぜこれでいいのかは後で解説します。

グリッド上の連続した長方形領域をビット列で表現する

グリッド上の連続した長方形領域をビット列で表現する方法があります。

グリッドの高さを H、グリッドの幅を W、長方形領域の高さを a、長方形領域の幅を b とします。

まず W をインクリメントします。

そして (((1 << (a * W)) – 1) / ((1 << W) – 1)) * ((1 << b) – 1) とすれば、これでグリッド上の連続した長方形領域をビット列で表現することができるのです。

W をインクリメントする理由ですが、こうすることで行末に番兵を置くことができます。行末に番兵を置くことで行境界をまたぐ事故(番兵なしだと横端と次行頭がつながってしまい、矩形判定がうまくいかない場合がある)を防ぐことができます。また << W で「真下」が簡単に表現できるようになります。

s /= (s & -s) の意味

s & -s は最下位の 1 ビットだけを取り出すという有名なテクニックです。s を割り切る最大の 2 のべき乗を得ることができるのです。

たとえば s = 40 であれば二進数表記すると 00101000 となります。-s = -40 なら 11011000 です。

s & -s を計算すると、

00101000
11011000
——–
00001000 = 8

となります。8 は 40 を割り切る最大の 2 のべき乗です。

s /= (s & -s) を計算すると

00000101 = 5

となりますが、これは 40 = 00101000 の末尾の 0 をすべて取り除いたものと同じです。

グリッド上の連続した長方形領域をビット列との関連でいえば、すでにタイルが置かれている部分を切り捨てて、これを平行移動させたものを取得することができるのです。その状態で新たに置こうとしているタイルを x として (s & x) == x であるなら既存のタイルと重なることなくそのタイルを置くことができることになります。

最後に

AC コードを再掲します。