Statue Park(スタチューパーク) を、5 つのルールセット内蔵でブラウザに実装した。手掛かりは「ポリオミノの袋」そのもの。盤面のマスを塗り、塗ったマスの連結成分の形の多重集合が、与えられた袋とちょうど一致するようにする。塗らなかったマス=公園は 1 つに繋がっていなければならない。ソルバー内蔵パズル第 50 弾。
デモ: https://sen.ltd/portfolio/statue-park/
リポジトリ: https://github.com/sen-ltd/statue-park
ルール
H×W の盤面のいくつかのマスを塗る。
- 塗ったマスは辺で繋がった塊に分かれる。その形の多重集合が、与えられた**袋(bank)**とちょうど一致する。回転・鏡像は自由、各ピースはちょうど 1 回ずつ使う
- 塗らなかったマス(公園)は、全体で辺により 1 つに繋がる
- 黒丸のマスは塗る。白丸のマスは塗らない
数字は 1 個も出てこない。手掛かりは「袋の中身」と「丸」だけだ。
いちばん有名なルールは、ルールではない
Statue Park の解説はどれも「2 つのピースは辺で接してはいけない」をルールとして並べている。これはルールではない。
塗ったマスの極大連結成分は、定義上、別の極大成分と辺で隣接しない。極大とはそういう意味だ。つまり答えを「配置の割り当て」ではなく**塗り分け(shading)**だと思った瞬間、非接触ルールは無料になる。
残るのは、ルールブック側のモデルがピースに名前を付けているという点だけで、その名前は観測不能だ。したがって:
同じ形の多重集合を持つ 2 つの袋は同じ塗り分けを与え、名前付きモデルの解数 = 塗り分けの解数 × ∏ mₜ!(mₜ は形 t の重複度)。
これは「そう解釈できる」ではなく、測れる主張だ。リポジトリには伝播器と 1 行もコードを共有しない 2 つの総当たりエンジンが入っている。5×5 で:
| 袋 | 塗り分け | 名前付き | 比 |
|---|---|---|---|
| 同じテトロミノ × 2 | 48 | 96 | 2 = 2! |
| 同じテトロミノ × 3 | 16 | 96 | 6 = 3! |
| A×1 + B×2 | 152 | 304 | 2 = 2! |
実務上の見返りは 2 つある。対称性除去がどこにも要らないこと(同一ピースの並べ替えを潰す小細工は塗り分けモデルには存在しない)、そして2 つの答えは必ずマスの集合として違うこと。後者が次の節の前提になる。
定理: 黒丸だけでも白丸だけでも、必ず一意にできる
同じ袋に対する 2 つの答えは、塗ったマスの個数が等しい。袋が合計面積を固定するからだ。だから 2 つの答えが違うなら、
- 答え A で塗られていて B で塗られていないマスがある
- 同時に、B で塗られていて A で塗られていないマスもある
(個数が同じなのだから、片側だけということはあり得ない。)
前者に黒丸を打てば B が死ぬ。後者に白丸を打てば B が死ぬ。よって黒丸だけの手掛かり集合も、白丸だけの手掛かり集合も、どんなパズルにも必ず存在する。
存在は決着した。残るのは値段だ。
測定: 黒丸は白丸より 6 割安い
同じ貪欲チューザ(第 2 解と食い違うマスを 1 個選んで丸を打つ)、同じ最小化、変えるのはアルファベットだけ:
size n mixed black-only white-only 黒丸の割合(mixed) 盤面の黒マス率
6x6 40 6.0 5.1 8.1 55.2% 36.2%
8x8 23 13.0 11.3 18.6 63.0% 34.7%
白丸だけだと 8×8 で 18.6 個、黒丸だけなら 11.3 個。6 割の差がある。理屈は後から言える: 白丸は「ここに像は無い」としか言わず、配置カタログから候補を 1 個ずつ削るだけだが、黒丸は「ここを像が通る」=ピース 1 個まるごとについての主張で、その 1 マスを覆えない配置を全部殺す。
面白いのは 3 列目だ。黒丸だけに制限した方が、自由に選ばせるより安い(8×8 で 11.3 対 13.0)。貪欲チューザは食い違うマスから一様に選ぶので、放っておくと安い白丸を引いてしまう。制約を足したら成績が上がった、という素直な例になっている。
裏づけとして、自由選択で残った手掛かりの黒丸率は 55〜63% なのに、盤面の黒マス率は 35〜36% しかない。最小化を通り抜けるのは黒丸の方だ、と生成器も言っている。
5 段のラダー
| 段 | 見てよいもの |
|---|---|
clue |
丸と頭数(袋が塗るマスの総数を決める) |
place |
合法配置のカタログ。配置は「自分のマスが公園と分かった」「自分に接するマスが黒と分かった」時点で死ぬ |
fit |
黒マスはちょうど 1 個のピースに属する。だから、生き残った配置のどれもがその黒マスと同居させない隣接マスは公園 |
white |
公園の連結性。公園を分断する切断点も含む |
probe |
上の全部に対する単集合整合性 |
各段は健全(fixpoint が答えと食い違わない、テストで表明)かつ単調(強い段が決めるビットは減らない)。
増分: probe 未満で完答する盤は 1 つも無い
一意性を買った最小手掛かり(=編集者が刷るであろう盤)での fixpoint 確定率:
size n clue place fit white probe 完答数
6x6 40 16.7% 18.8% 25.3% 29.1% 74.0% clue:0 place:0 fit:0 white:0 probe:26
8x8 24 20.5% 22.6% 25.7% 29.2% 48.9% clue:0 place:0 fit:0 white:0 probe:5
下 4 段はどれも単独では 3 割弱で止まり、probe で急に跳ねる。clue〜white グレードのパズルは 1 枚も出てこない。 出荷する盤に難易度の階段を持たせたければ、手掛かりを足して弱い段でも完答できるところまで持っていくしかない(本リポジトリの生成器はそうしている)。そのコストは驚くほど安く、
size n unique-min solve-min 比
6x6 40 6.0 6.5 1.08
8x8 24 13.1 14.3 1.09
一意にするのに要る丸の 1 割増しで、探索なしで解ける盤になる。一意性はほぼタダで探索不要性を連れてくる。
ablation: 4 段とも効いている
同じ盤で、フルスタックから 1 段だけ抜く:
size variant fixpoint-bits 動いた盤 probe-仮定 探索-仮定
6x6 full 1440 0 3098 0
6x6 −count 1426 1 3385 2
6x6 −place 736 35 5461 2136
6x6 −fit 834 25 6368 122
6x6 −white 629 36 4289 1002
8x8 full 1536 0 4274 0
8x8 −count 1404 6 4543 28
8x8 −place 845 24 6122 23790
8x8 −fit 807 21 7804 254
8x8 −white 528 24 4741 5700
面白い行は count だ。頭数は fixpoint ではほぼ無料(6×6 で 40 盤中 1 盤しか動かない、40 盤合計 14 ビット)なのに、抜くと探索が仮定を買い始める(8×8 で 0 → 28)。増分だけを見ていたら「要らない」と結論しただろう。逆に place は 8×8 で抜くと探索の仮定が 23,790 回に爆発する。
曖昧さは「ピースが 1 個ずれる」ではない
丸を 1 個ずつ剥がして曖昧になった瞬間の第 2 解を、元の答えと成分単位で比べる:
size 曖昧盤 1個だけ動く 2個 3個以上 平均
6x6 40 17.5% 42.5% 40.0% 2.23
8x8 24 16.7% 25.0% 58.3% 3.13
この連作では「第 2 解の 9 割を、探索ゼロの小さな証明書(2 マスのスワップ、2-opt 反転、行 1 本の平行移動)が説明する」という結果が続いていた。Statue Park はそうならない。第 2 解は平均 2〜3 個のピースを動かし、1 個だけずれる盤は 6 分の 1 しかない。安い局所証明書が無い、というのがここでの答えで、袋という手掛かりが大域的(どのピースがどこにあるかは他の全ピースの位置に依存する)である以上、たぶんこれが正しい姿だ。
公園ルールはどれくらい効いているのか
「塗り方の袋制約」だけを満たす盤と、そこに公園の連結性を課した盤の解数を、手掛かりゼロで比べる:
config boards 公園あり 公園なし 殺した割合
6x6 2x size4/5 30 833 1399 40.4%
6x6 3x size4 30 5730 15149 62.2%
6x6 3x size5 30 704 4431 84.1%
7x7 3x size5 30 18744 57546 67.4%
ピースが大きいほど公園ルールの取り分が増える(6×6・ペントミノ 3 個で 84.1%)。同じ 3 個に 7×7 の余裕を与えると 67.4% に落ちるので、効くのは盤が窮屈なときだ。いずれにせよ「連結性は飾りではなく解空間の主要な削り手」という数字は取っておく価値がある。
生成器は袋を推測しない
分割やヒントを先に置いて「解があるか」を祈る作りにはしていない。答え先行だ:
- ピースを 1 個ずつランダムに落とす。既存ピースに接する場所は禁止
- 1 個落とすたびに公園の連結性を確認し、切ってしまう置き方は採らない
- 全部置き終わった時点で、それはもう合法な答え。袋はその答えから読み取る
合法性は構造で保証されるので、買うのは一意性だけになる。あとは第 2 解を見て食い違うマスに丸を打ち、最後に丸を削る。
健全性
- 3 エンジン一致: 伝播つき探索、配置先行の総当たり、ピースに名前を付けないマス先行の総当たり。6×6 の 80 通りの手掛かり集合で解数が 3 者一致、不一致 0。ラダーの各段でも解数が変わらないことを別途確認
-
外部台帳: 袋を「1 マスピース × k 個」にして公園ルールを切ると、答えはちょうど格子グラフの大きさ k の独立集合になる。k について足し上げると
2, 7, 63, 1234, 55447——OEIS A006506。テストは k ごとに独立集合カウンタと突き合わせ、合計も照合する - 1×m 帯の閉じた式: 帯では公園が連結 ⇔ 白マスが連続、なので黒マスは接頭辞+接尾辞。1 マスピースなら両端が高々 1 個ずつで、解数は k=0 で 1、k=1 で 2、k=2 で(m≥3 なら)1、k≥3 で 0。全 m で再現
-
probe⇔ 一意: 6×6 で 300 通り、8×8 で 364 通りの手掛かり接頭辞を掃いて、「probeが完答したのに一意でない」は 0 件。逆向き(一意なのにprobeが止まる)は 6 件と 4 件あり、一致率 98.0% / 98.9%。probeは嘘をつかないが、全部は見えない
テストは 69 本。
実装の小さな教訓
いちばん時間を食ったのは、名前を付けない方の総当たりエンジンだった。マスを読み順に走査して塗る/塗らないを決め、塊が閉じた(周囲が全部確定した)瞬間にその形を袋から差し引く、という設計にしていた。
バグは、閉じた塊を毎ステップ再計算していたことだ。ステップ i で閉じた塊は、ステップ i+1 でも「閉じている」ので、同じピースを袋から二度引いてしまう。しかも症状が出るのは「同じ形が 2 つ以上ある袋」だけで、しかも解が減る方向にしか壊れない——静かに間違える最悪の型だ。
直し方は「袋に請求済みのマス」を 1 本持って、バックトラック時に戻すだけだった。教訓はいつもの形をしている: 総当たりエンジンは「速い実装が正しい」ことを確かめるために書くのに、総当たりエンジン自身がいちばん静かに壊れる。 だから健全性の主張を 1 本のエンジンに載せてはいけない。ここでは配置先行エンジンが同じ間違いを構造的にできない(配置は袋のスロットに 1 個ずつ割り当てられるので二重請求が起きようがない)ので、2 者の解数一致がそのままこのバグの検出器になる。
src/statue-park.ts モデル・形・配置カタログ・5 段・レフェリー
src/brute.ts 伝播器と 1 行も共有しない 2 エンジン(名前付き / 塗り分け)
src/generate.ts 答え先行のレイアウトと丸の売買
tools/stats.mts この記事の全ての表
デモ: https://sen.ltd/portfolio/statue-park/
リポジトリ: https://github.com/sen-ltd/statue-park
