目次

AtCoder Beginner Contest 336 F,G問題メモ

AtCoder Beginner Contest 336

今や、Eくらいの桁DPだと水Diffなんですねえ。しっかり青ありそうと感じていたが。

F - Rotation Puzzle

F - Rotation Puzzle

問題

解法

1回の操作でとりうる選択肢は、4通りしかない。

まず、「たとえば ↖左上、↗右上 の順に操作するとシンプルな1つの操作と見なせる」みたいな、 一見、複雑な操作でも2個まとめることでわかりやすくならないかな? と考えたが、 実際にやってみても今ひとつシンプルにならない。

じゃあ、制約が小さいので、盤面まるごと状態に持ったBFSはどうか、というと、 こちらは 20回で見つからなければ打ち切っていいという設定もあり、いけそう。

だが、状態数を見積もると、直前に行った操作を繰り返すのは無駄なので2手目以降は選択肢が3つに減るとしても、 20手までの全状態は $\displaystyle \sum_{i=0}^{19} 4 \times 3^i \simeq 7 \times 10^9$ となり、さすがに制限時間が厳しい。

しかし、10手くらいなら $\displaystyle \sum_{i=0}^{9} 4 \times 3^i \simeq 1.2 \times 10^5$ 程度なので、 実際に盤面を反転させる操作に $HW$ かかるとしても、十分に全探索できる。

初期盤面と目標盤面のそれぞれから上限10手分だけ探索を進めると、 両方から同じ盤面に到達できる中で、合計最短手数の少ないものが答えとなる。

多分、問題が最初からグラフの形だったら「双方向探索しなさい」という意図が分かりやすくなってもう少し簡単になってそう。
盤面を回転させる、盤面ごと状態に持つ、という重実装系の要素が目眩ましとなっていた感がある。

実装

Numpyを使うと、2次元配列の反転は a[::-1, ::-1] と書けるので、幾分か実装が楽。

だが、BFSで距離などを記録する際、盤面のnumpy.arrayをそのまま辞書型のキーにすることはできない。(Hashableでないので)

a.data.tobytes() とすると、盤面aの状態を一意に表すHashableな値に、そこそこ高速に変換できる。

Python3

G - 16 Integers

G - 16 Integers

問題

解法

公式Editorialを参考にしての自分なりのメモ。

テクニック(公式)を知っていないとわりとどうにもならない(本番中に自力で導出・証明するのは現実的で無さそう)。
また、問題をそのテクニックに適用するにも考察が必要。

ただ、テクニックへの当てはめ方さえ分かれば、アドホックに実装する部分は少なめかな。

グラフの問題に変換

3つの並び $(0,0,0)~(1,1,1)$ を表す8頂点を用意し、例えば $(0,1,0,1)$ は $(0,1,0)→(1,0,1)$ の移動として捉える。

すると、「グラフ上のどこか適当な頂点から開始し、$(i,j,k)→(j,k,l)$ への移動を合計 $X_{i,j,k,l}$ 回、全ての $(i,j,k,l)$ に対しておこない、適当な頂点で終了する」ような動きを、何通り作れますか、という問題になる。

さらに移動回数分だけ多重辺として複製してやると、 「全ての道を1回ずつ通る経路(オイラー路)が何通りありますか」という問題になる。

有向グラフがオイラー路を持つ必要十分条件は、以下となる。

これに該当しない入力は“0”と出力して終了。
以下、この条件は満たすとする。

BEST Theorem

有向オイラーグラフに対して、そのオイラー閉路を数え上げる公式。

ここで、似た言葉が出てくるので確認しておくと、有向グラフにおいて、

今回求めたいのは“オイラー路”の数だが、BEST Theorem が求めるのは“オイラー閉路”の数。
さて、どうするか。

オイラー路の始点と終点を固定すると、終点→始点 に辺を1本追加した経路はオイラー閉路となる。
辺は全て区別されるので、「辺を追加したグラフでオイラー閉路を1つ見つけ、追加辺で切り開く」とオイラー路と1対1対応する。
これで、BEST Theorem からオイラー路を数えられる。

始点・終点の決め方のパターンとしては、

BEST Theorem では、オイラー閉路の数は以下の式で与えられる。

無向グラフの行列木定理ではラプラシアン行列の余因子を求めるが、やることはよく似ている。
有向グラフの場合、行列の作り方、削除する行と列は $i=j$ でないとダメな点、(オイラーグラフでない一般の場合)$i$ によって結果が変わる点、などが無向グラフとは異なるが、ある程度は有向グラフでも無向版のライブラリを流用できる。

行列木定理ではループは考慮されないので、 ループだけしか含まれない入力($X_{0,0,0,0}$ のみ正で、他は0など)では、 行列式を計算すべき行列が空になり、実装によっては変になりうる。
コーナーケースとして別途処理するのが安心かも。

Python3