Processing math: 27%

AtCoder Beginner Contest 336 F,G問題メモ

AtCoder Beginner Contest 336

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

F - Rotation Puzzle

問題

  • H×W の盤面に、1HW の数字が1つずつ書かれている
  • 以下の操作を20回まで行える
    • 盤面より縦横1つ小さい (H1)×(W1) の長方形を選び、それを180度回転する
  • 盤面を整列する(Row Major Order に 1,2,...,HW が書かれた状態にする)までの最小手数を求めよ
    • ただし、20回で不可能なら-1
  • 3H,W8

解法

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

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

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

だが、状態数を見積もると、直前に行った操作を繰り返すのは無駄なので2手目以降は選択肢が3つに減るとしても、 20手までの全状態は 19i=04×3i7×109 となり、さすがに制限時間が厳しい。

しかし、10手くらいなら 9i=04×3i1.2×105 程度なので、 実際に盤面を反転させる操作に HW かかるとしても、十分に全探索できる。

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

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

実装

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

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

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

Python3

G - 16 Integers

問題

  • X0,0,0,0X1,1,1,1 の、4つの {0,1} を添字に持つ16個の整数が与えられる
  • その合計値を N とする
  • 以下の条件を満たす長さ N+3 の数列 A=(A1,...,AN+3) としてあり得るものの個数を、\mod{998244353} で求めよ
    • A の要素は全て \{0,1\}
    • 全ての (i,j,k,l) に対して、A 中で (i,j,k,l) の並びはちょうど X_{i,j,k,l} 個ある
      • 例えば、A 中で ...,0,1,0,1,... という並びが出現する箇所は X_{0,1,0,1} 個ある
  • 1 \le N \le 10^6

解法

公式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でない)全ての頂点が連結
  • 各頂点の入次数と出次数について、以下のいずれか
    • 全ての頂点について、入次数と出次数が等しい
    • 入次数が出次数より1だけ小さい頂点が1つ、1だけ大きい頂点が1つあり、他は全て等しい

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

BEST Theorem

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

    • “BEST” は、提案した4名の名前のイニシャルから取られているらしい
  • 適用条件
    • 自己ループや多重辺を含んでいてもよい
    • 頂点と辺は全て区別する(多重辺であっても)

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

  • オイラー路
    • 全ての道を1回ずつ通る経路。始点と終点が別でもいい
  • オイラー閉路(オイラー回路)
    • 全ての道を1回ずつ通り、始点に戻ってくる経路
  • オイラーグラフ
    • オイラー閉路を持つようなグラフ
    • =全ての頂点で、入次数=出次数となっているグラフ

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

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

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

  • グラフに入次数が出次数より1小さい/大きい頂点が1つずつある場合、そこが始点/終点で確定する
  • 全ての入次数と出次数が等しい場合、そこから作られるオイラー路は既にオイラー閉路であり、どの頂点でも始点・終点と見なせる。
    • 始点かつ終点とする頂点(最大8通り)を全て試し、結果を合計すればよい

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

  • \displaystyle ec(G)=t_{w}(G) \prod_{v \in V}(deg(v)-1)!
  • t_{w}(G) は、頂点 w を根とする有向全域木の個数
    • 行列木定理(有向グラフバージョン)によって求められる
      • 以下の正方行列を作成する
        • q_{i,j} には i→j への辺の数の-1倍
        • q_{i,i} には i の自己ループを除いた入次数
      • i 番目の行と列を除いた行列の行列式が、t_{i}(G)
    • オイラーグラフの場合、w に依らず全頂点で同じ値になる
  • deg(v)v からの出次数

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

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

Python3

programming_algorithm/contest_history/atcoder/2024/0114_abc336.txt · 最終更新: 2024/01/23 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0