0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

数コロ(Sukoro)を解く — 数字が「答えの関数」になっているパズルでは、手掛かりは肯定文しか書けない

0
Posted at

**数コロ(Sukoro)**を、4 段のソルバー内蔵でブラウザに実装した。ルールを言い換えると、数字はそのマス自身の次数になる——つまり数字は形の上に乗った情報ではなく、形の関数だ。答えは「連結で、隣り合う頂点の次数が全部違う(locally irregular な)誘導部分グラフ」そのものになる。ここから出たのは、この連作で初めて見る種類の非対称性だった。手掛かりは「ここに数字がある」としか言えず、「ここは空だ」と書く記法が存在しない。 結果、印刷可能な手掛かり集合 1,254,128 通りを全数え上げすると **91.5% が「そもそも解が存在しない」**で、パズルになっているのは 1.2% しかない。一方で「答えから読み取った手掛かり」に限れば解無しは原理的に起こらず、手掛かりを 1 個消しても盤は絶対に壊れない。ソルバー内蔵パズル第 57 弾。

デモ: https://sen.ltd/portfolio/sukoro/
リポジトリ: https://github.com/sen-ltd/sukoro

Sukoro

ルール

  1. いくつかのマスに 1〜4 の数字を書く。残りは空欄
  2. 数字は、そのマスの上下左右の隣で数字が入っているマスの個数を表す
  3. 隣り合う 2 つの数字は異なっていなければならない
  4. 数字の入ったマス全部がひとつながりになる

以上。いくつかの数字が最初から印刷されている。

数字は答えの上に乗っているのではなく、答えそのもの

ルール 2 をグラフの言葉で読み直す。「隣で数字が入っているマスの個数」とは、数字マスが誘導する部分グラフでの、そのマスの次数のことだ。つまり数字は形の上に別途書き込まれた情報ではなく、形が決まれば自動的に決まる関数になっている。

そうするとルール 3 は「この部分グラフのどの辺も、両端の次数が異なる」と読める。これはグラフ理論で locally irregular graph(局所非正則グラフ) と呼ばれているものの定義そのままだ。

答え = 格子グラフの、連結で最小次数 1 以上の、locally irregular な誘導部分グラフ

この 1 文からほぼ全部が出てくる。まず 3 つ。

  • 孤立した 1 マスは違法。次数 0 になるが、0 は数字ではない
  • ドミノ(2 マス)も違法。両方 1 になり、しかも隣接している。K₂ は「locally irregular でないグラフ」の教科書的な例で、それが紙の上に出てくる。よって最小の合法な塊は 3 マスで、1–2–1 と読む
  • 内点を 2 つ持つベタ塗りは違法。どちらも 4 になり、隣り合う

答えは細くもなれないし、ベタにもなれない。だから盤をどこまで埋められるかに上限が出る。全数え上げで測ると 2×4〜5×5 では 75〜81%。そして例外は全部で 2 つしかない。8×12 までの全長方形を調べて、完全に埋め尽くせるのは 1×3 と 3×3 だけだった。ベタ塗りの 3×3 は

2 3 2
3 4 3
2 3 2

と読む。内点が 1 個しかないので「4 の隣にもう 1 つの 4」が発生しない。まぐれで locally irregular になっている。

UI もこの事実から決まる。 数字を打ち込むテンキーが存在しない。マスをクリックすると「数字 → 除外 → 未定」と 3 状態を巡回するだけで、数字は勝手に出てくる。いま自分が描いた形の、隣接数字マス数だからだ。

1. 白紙の盤 — 1×n はちょうど n − 2

手掛かりを 1 個も置かずに「空の h×w 格子に答えは何通りあるか」を全数え上げした。これは答えというオブジェクトそのものの個数を数えることでもある。

1 2 3 4 5 6 7 8 9 10
1×n 0 0 1 2 3 4 5 6 7 8
2×n 0 4 12 26 48 80 126 190 278 398
3×n 1 12 54 162 428 1,012 2,322 5,134 11,338 24,580
4×n 2 26 162 702 2,810 10,514 38,928 143,254
5×n 3 48 428 2,810 18,230 110,138 666,757
6×n 4 80 1,012 10,514 110,138 1,074,750
7×n 5 126 2,322 38,928 666,757

全部厳密値(下界は 1 つも無い)。小さいものは毎回のテスト実行でゼロから再計算し、4×4 までは 2 本目のエンジンで数え直している。

1 行目がルールの縮図になっている。1×n の帯の答えはちょうど n − 2 通りだ。1 行では塊は連続する run にしかならず、run は 1, 2, 2, …, 2, 1 と読む。長さ 1 は 0 が出るので違法、長さ 2 は 1 が 2 つ隣接して違法、長さ 4 以上は 2 が 2 つ隣接して違法。長さちょうど 3 だけが生き残り、置き場所が n − 2 箇所ある。

ついでにこれは、手掛かりが 1 個も無くても成立する唯一の盤が 1×3 だということでもある。真っ白な 1×3 は答えがちょうど 1 通り。

2×n 以降はどれも OEIS に無い。2×n の行はしばらく多項式に見えるが違う。2×2 〜 2×5 の 4 項を通る 3 次式は 2×6 に 80 を出して当たり、2×7 に 124 を出して外す(真の値は 126)。

2. 手掛かりは「ある」としか言えない

このパズルの手掛かり言語が変なのはここだ。印刷された数字は「このマスには数字があり、それはこの数字だ」と主張する。「このマスは空だ」と書く記法が存在しない。 手掛かりは全部が肯定文で、作者は答えにマスを足すことしかできない。ここから逆向きの 2 つが出る。

補題: 全部印刷すれば必ず一意

答えの数字を全部印刷すると、その盤の答えはちょうど 1 通りになる。

証明は 3 行で済む。対抗する答え S′ は手掛かり集合 S を全部含まなければならない(手掛かりがそう主張しているので)。もし S′ が真に大きいなら、連結性より S′ \ S のあるマスが S のどれかのマス s と隣接する。すると s の次数は印刷された数字より 1 以上大きくなって矛盾。よって S′ = S。数字は次数なので、数字も一致する。∎

ランダムに引いた答え 100 個で確認して 100/100。そして連結ルールを切ると 39/100 に落ちる。この補題は実質「連結ルール」なのだ。

印刷できるもののほとんどは意味を持たない

手掛かり集合は部分写像だ——マスをいくつか選んで、それぞれに 1〜4 を書く。小さい盤ならこれは有限アルファベット × 有限位置なので、作者が印刷しうる手掛かり集合を 1 つ残らず列挙して解ける。全部で 1,254,128 通り歩いた。

手掛かり数 手掛かり集合の数 解無し 複数解 ちょうど 1 解
3×4 1 48 29.2% 70.8% 0.0%
3×4 2 1,056 65.8% 27.0% 7.2%
3×4 3 14,080 91.2% 5.0% 3.8%
3×4 4 126,720 98.5% 0.6% 0.9%
4×4 2 1,920 58.6% 40.1% 1.2%
4×4 4 465,920 96.6% 2.3% 1.1%
4×5 3 72,960 77.6% 20.4% 2.0%
5×5 2 4,800 42.9% 57.0% 0.2%
5×5 3 147,200 68.5% 30.8% 0.6%

(抜粋。全表はデモページにある)

印刷可能なものの 91.5% は「解が無い」。パズルになっているのは 1.2% しかない。しかも死んだ空間は手掛かりを増やすほど広がる——3×4 に 4 個で 98.5%。数字を 1 個足すたびに「格子には実現できないこと」を主張してしまう機会が 1 つ増えるからだ。

そして手掛かり 1 個ではどの盤も一意にならない。352 通りの 1 手掛かり盤のうち、一意なものは 0

3. 同じ調査を「嘘でない手掛かり」に絞る

嘘を取り除くと質問が変わる。空盤の答えを全部列挙し、その答えの数字の部分集合を手掛かり集合として全部試す。これらは構成上「解無し」になりえない(元の答えが常に成立する)ので、残る失敗は曖昧さだけ——作者にとっての本当の問題だ。

答えの数 歩いた真の手掛かり集合 一意化できる最小個数 答えあたり中央値 最悪の答え
3×4 162 13,582 2 3 4
3×5 428 131,428 2 3 6
4×4 702 261,346 2 3 6

数字 2 個で答えが決まることがあるし、答えあたりの中央値は 3 個。上の全数え上げの表とはまるで違う絵になる。この 2 つの表の差こそが手掛かり言語の正体で、作者は「手掛かり集合の空間」ではなく「答えの空間」を探索し、そこから数字を捨てている

大きい盤ではどうなるか

サンプリングで盤を大きくすると絵はさらに極端になる。

手掛かり数 ランダムな手掛かり: 解無し ランダム: ちょうど 1 解 真の手掛かり集合: ちょうど 1 解
6×6 3 53.0% 0.2% 4.8%
6×6 12 100% 0% 21.1%
8×8 8 95.0% 0% 0%
8×8 25 100% 0% 16.9%
10×10 8 85.0% 0% 0%
10×10 25 100% 0% 0%

ランダムに数字を撒くのは、面白くなる前に破綻する。全サイズ・全手掛かり数を通じて 4,940 回サンプリングして、パズルになったのは 2 回だけ。8×8 以上では 1 回も出ない。

「答えから読み取った手掛かり」でもまだ足りない。10×10 では答えの数字のランダムな部分集合を 66 回引いて、一意になったのは 0 回(手掛かり数 3〜25 の全域にわたって)。サンプル数は小さい(10×10 の答えをランダムに引くこと自体が高い)が、一度も当たらなかった。一方で出荷している 10×10 盤は 16〜22 個の数字を印刷して毎回一意になる。何個残すかと同じくらい、どれを残すかが効いている。

だから生成器はサンプリングをしない。答えを 1 つ描き、それを全部印刷し(補題によりこの時点で必ず一意)、一意である限り数字を 1 個ずつ消していく。前回のヘテロミノではこの最小化フェーズが原理的に書けなかった(手掛かりを 1 個減らす合法な一手が存在しなかった)。数コロでは 3 行で書ける。

4. 数字の価値と、位置の価値

印刷された数字は同時に 2 つの仕事をしている。「このマスには数字がある」と言う仕事と、「その数字はこれだ」と言う仕事だ。この 2 つは分離して測れる。 手掛かりを全部「印(ここに数字はあるが、いくつかは言わない)」に置き換えると、1 つ目だけが残る。

出荷時の手掛かり(数字あり) 同じマス・数字を消す 答えの全マスに印・数字なし 答えの全マスに数字
6×6 24 / 24 0 / 24 0 / 24 24 / 24
8×8 24 / 24 0 / 24 0 / 24 24 / 24
10×10 24 / 24 0 / 24 0 / 24 24 / 24

これは予想より強い結果だった。出荷盤の手掛かり位置から数字だけ消すと、72 盤すべてが一意性を失う。さらに答えのマスを全部印で示しても(つまり答えの形を完全に教えても)、やはり 0/72。一方で同じマスに数字を書けば 72/72 で一意(さきほどの補題)。

答えの形を全部知っていることは、答えを知っていることではない。 印だけの盤は、その形をさらに拡張した別の合法な形をいくらでも許してしまう。数字が入って初めて次数が固定され、拡張が禁じられる。

5. ルールを片方ずつ落とす

ルールセット 一意性を失う盤 うち解が無くなる盤
何も落とさない 0 / 72 0
隣り合う数字が同じでもよい 72 / 72 0
数字が連結でなくてもよい 70 / 72 0
両方落とす 72 / 72 0

右の列が全部 0 なのは偶然ではない。ルールを緩めることは答えを増やすことしかできない——意図した答えは緩いルールセットでも依然として合法だからだ。この連作の他のパズル(前回のヘテロミノでは、ルールをきつくすると 72/72 が解無しになった)とは対称性が逆になっている。

連結ルールを落とすと 70/72 で、2 盤だけ生き残る。手掛かりの配置がたまたま「2 つ目の塊を置く余地」を残していなかった盤だ。そして前述のとおり、連結ルールを落とすと補題そのものが壊れる(100/100 → 39/100)。生成器はこの補題の上に立っているので、これは生成器の土台が連結ルールだという意味でもある。

6. 梯子 — 下 2 段は 1 盤も終わらせられない

出荷 72 盤を一意と証明するのに要した分岐回数を、段ごとに合計した。

deg neq conn probe
6×6, 24 盤 3,840,046* 48,958 932 2
8×8, 24 盤 9,497,265* 1,862,473* 5,280 0
10×10, 24 盤 9,600,024* 7,618,877* 19,296 64

* は下界。1 盤あたり 400,000 分岐で打ち切っており、最下段はそのうち 53 盤(10×10 は 24 盤全部)で最後まで到達できなかった

手つかずの盤から各段が何マス確定できるか(デモページのチェックボックスが表示しているもの):

1 手も指す前に確定できる未印刷マス deg neq conn probe
6×6 11.5% 23.9% 27.2% 97.1%
8×8 10.2% 18.7% 21.3% 100%
10×10 10.7% 17.0% 19.1% 94.7%

各段の中身:

  • deg は純粋な算術。v を持つマスは数字の隣がちょうど v 個必要なので、v は「すでに数字と確定した隣の数」と「まだ数字になりうる隣の数」の間に挟まれる。逆向きも効く: 上限に達したら残りの隣は空、下限に達したら数字になりうる隣は全部数字
  • neq は locally irregular ルールを消去として書いたもの。確定した数字は隣で自分自身を禁止する
  • conn はこの梯子で唯一盤全体を一度に見る段。確定した数字マスに到達できないマスを全部空にし、さらに取り除くと確定マスが 2 つに分断されるマスに数字を強制する。これは「まだ数字になりうるマス」のグラフの関節点で、1 マスずつ外して試すのではなく Tarjan の関節点アルゴリズム 1 パスで全部拾う。この段だけで 3 桁効く
  • probe は singleton consistency。72 盤中 68 盤を探索ゼロで終わらせる

conn が効きすぎている理由は、この puzzle の連結性が「大域的だが局所的に判定できる」形をしているからだ。数字マスの塊は 1 つでなければならないので、まだ数字になりうるマスのグラフ上で確定マス同士を結ぶ橋は必ず数字になる。数字の値については何も言わないが、位置については強い。

7. どの数字が出てくるか

1 2 3 4
出荷 72 盤の答え 24.5% 38.0% 27.3% 10.2%
その盤が印刷している数字 42.8% 24.0% 26.6% 6.6%
空の 5×5 の全答え 37.0% 34.8% 23.1% 5.2%

4 が少ないのは幾何で説明がつく。4 は隣 4 つ全部が数字で、しかもその 4 つがどれも 4 でないことを要求するので、周りに小さい数字の十字が必要になる。

予想外だったのは 2 行目だ。 答えの中の数字の分布と、その盤が実際に印刷している数字の分布が一致しない。答えの 24.5% が 1 なのに、印刷されているものの 42.8% が 1。最小化フェーズは数字が何であるかを一切見ておらず、ただ「消せるものを消して、消せないものを残す」だけなのに、こうなる。

理由を後から考えると納得できる。1 は「このマスの数字の隣はちょうど 1 個」と言うので、周囲 3 マスを一気に空へ落とす。4 は「隣 4 つ全部が数字」と言うので、1 マスも空にしない。 消去に強い手掛かりが生き残る。

8. 手掛かりを消しても盤は壊れない

手掛かりが純粋な肯定文であることの、もう 1 つの帰結。手掛かりを 1 個消しても、元の答えは残りの手掛かりを依然として満たすので、盤は答えが増えることはあっても、最後の 1 つを失うことは絶対にない。

消した手掛かり 残る解の数(中央値) 解無しになった盤 代わりに動かした手 まだ一意 解無しになった
6×6 54 6 0 162 3.1% 80.2%
8×8 90 8+ 0 270 2.2% 73.7%
10×10 153 8+ 0 459 2.4% 82.8%

(解の数え上げは 8 個で打ち切っているので 8+ は「8 個以上」の意味)

297 回の消去のうち、盤を解無しにしたものは 0 回。議論どおりだが、確かめる価値はある。そして消去は全部一意性を壊す——だから出荷セットは極小だ。

一方、手掛かりを別のマスへ動かすとこの保護は消える。一意性が保たれるのは 2〜3% で、74〜83% は盤を解無しにする。全数え上げで見た「印刷可能なものの 91.5% は解無し」を、うまくいっている盤の内側から見ているのと同じことだ。消すことと動かすことは同じ大きさの編集だが、まったく違う種類の編集になっている。

9. エンジンは 2 本、1 行も共有しない

エンジン 1 は各マスに 5 通りのドメイン(空・1・2・3・4)をビットマスクで持たせ、4 段を不動点まで伝播させてから候補最小のマスで分岐する。

エンジン 2 はドメインを作らない。格子を読み順に走査してマスあたり 1 ビット(数字を持つか否か)だけ決め、あるマスの近傍が全部決まった瞬間に、そのマスの次数を盤から直接読んで検査する。

このエンジンがこう書けるのは、まさにこのパズルの肝のおかげだ。形を選べば数字は決まるので、エンジン 2 は一度も「値」を選ばない。エンジン 1 が小さい盤で報告する解数は、テストで全部エンジン 2 と突き合わせている。出荷 72 盤も全部エンジン 2 で検算済み。

実装

TypeScript + Vite、ランタイム依存ゼロ。

src/sukoro.ts     ルール、4 段、エンジン 1
src/brute.ts      エンジン 2(1 行も共有しない)
src/generate.ts   答えの探索、手掛かり最小化、手掛かり空間の調査
src/ledger.json   白紙盤の答え数        (npm run ledger)
src/puzzles.json  出荷 72 盤            (npm run generate)
src/stats.json    この記事の全数値      (npm run stats)
tools/notes.mts   stats.json からデモページの解説を生成

デモページの数値は全部 tools/notes.mtssrc/stats.jsonsrc/ledger.json から生成している。手で書き写した数字は 1 つも無い。 全 53 テスト。

ソルバー内蔵パズル第 57 弾。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?