「黒峰問題」と界隈で呼ばれている問題をご存知でしょうか。具体的には以下の問題です。
次を満たす整数 をすべて求めよ。
この問題について、実は1年ほど前に「解決宣言」をしています↓
#黒峰問題 が解決しました!だまシ氏( @dama_math )とふりっぐ氏( @41078129 )に大きく感謝します。6ページあるので続けてツイートします。 pic.twitter.com/J13n9DLh24
— すむーずぷりんちゃん🍮 (@mat_der_D) August 8, 2019
証明の大きな流れは以下のツイートの図にまとまっています。
一か所数字を間違えていたので直しました
— すむーずぷりんちゃん🍮 (@mat_der_D) August 8, 2019
(12029→12096) pic.twitter.com/Hj0ZOTqw3Q
最近 Julia を使ってこの証明を追試したところ、うっかり計算ミスを発見してしまいました。さらに悪いことに、この欠陥を未だ修正できていません。
この記事は、以上の経緯と具体的な内容について、以下の四段構成でお送りします。
黒峰問題の基本的性質と基本手筋
改めて黒峰問題を眺めてみましょう。
次を満たす整数 をすべて求めよ。
さらにいろいろ実験してみると「これ以外なさそうだな」という風に予想したくなります。この予想が正しいか?というのが黒峰問題の中心的関心です。
これに対して武器になるのはズバリ です。数多の をとることで、 が満たすべき条件を絞っていきます。
簡単な議論により, がいずれも非負の場合を考えれば十分であることが分かるので、以下ではこれを仮定します。
具体例をいくつか示します。まず の場合を考えます:
次に の場合を考えましょう。方程式は
の威力は続きます。 の場合に を考えると、方程式は
このような感じでどんどん条件を絞り込んで行きます。 が確定したので を考え、さらに を考えれば、
前回の解決宣言の際には、これをさらに分解して
Julia のコードを使って黒峰問題を再検証した話
Julia を使うと言っても、作戦がなければなにもできません。ここでは典型例の に着目して作戦を整理します。
解決宣言でのフローでは、 の順番に を取ればよい、と書いてあります。まず前情報がないと考えて を考えます。 での の位数*2は であり、 の位数は です。したがって は で割った余りを、 は で割った余りを考えればよいことになります。そこで以下のような表を準備します。
の組み合わせとしては表のマスの数、すなわち 通りあります。それぞれについて を計算し、これが で と合同になるような整数 があるかどうかを調べます。条件を満たすものについて、表に○を書き入れます。以上により として条件を満たすものが求まります。さて、ここまで忘却していましたが、前情報として があったことを思い出します。この2つの情報を合わせるには の情報として合わせる必要があります。つまり の周期性を利用して、表のサイズを合わせる、という寸法です(サイズだけに)。サイズを合わせたら、2つの表の両方で現れている○だけを残して、全て削除します。結果的には のみが残ります。
この操作を実装したものが以下になります。
toy-box/kuromine_checker_neo.jl at master · mat-der-D/toy-box · GitHub
詳細は省きますが、sheet_gen(p) 関数で のときの表(にあたるもの)を作成し、 という演算により「表を合わせる」操作を実行できます。例えば上の操作は次のように書かれます。
julia> include("kuromine_checker_neo.jl") prod (generic function with 1 method) julia> s = sheet_gen(5, 5, 6, 6); julia> pr(s) mod:(6, 6) candidates: Set([(5, 5)]) julia> pr(s ⊗ sheet_gen(73)) mod:(18, 12) candidates: Set([(17, 5)])
また立て続けに表を生成して合わせていく操作も手軽に行えるよう、prod という関数も作りました。 の操作は以下のようになります。
julia> s = sheet_gen(5, 5, 6, 6); julia> pr(s) mod:(6, 6) candidates: Set([(5, 5)]) julia> pr(s ⊗ prod(73, 13, 37, 109, 433)) mod:(72, 108) candidates: Set(Tuple{Int64,Int64}[])
最後の candidates: のところが空の Set になっているので、この場合はすべて不適であることが判定されたことになります。
問題なく検証できたあと2ケースについても取り組んでみます。
- のケース
の順で考えます。事情により べきおよび べきの は先程のツールで扱えないので、は手計算で処理します*3。まず が確定しているので、 は必ず の倍数になります。よって
を考えることになります。でのの位数はです。これらに対応する の剰余のうち、立方剰余*4となるものを列挙すると
となり、それぞれ
になります。前情報とあわせれば
のみが残ります。あとは先程のツールで を組み合わせると
julia> s = sheet_gen(14, 5, 18, 6); julia> pr(s ⊗ sheet_gen(73)) mod:(18, 12) candidates: Set(Tuple{Int64,Int64}[])
となり、すべて不適になりました。
- のケース
これは一瞬です。以下のとおり をとると不適になります。
julia> s = sheet_gen(2, 3, 6, 6); julia> pr(s ⊗ sheet_gen(13)) mod:(12, 6) candidates: Set(Tuple{Int64,Int64}[])
検証の結果計算ミスが見つかった話
さて最後の のケースを検証してみましょう。このケースは既に見つかっている解のうち を含んでいることに注意してください。これが残っている限りは をいくらとっても不適にならないため、うまく外す必要があります。そこで を取ります。こうすることで、 と [x \geqq 6] を分解できます。
計算は読者の演習問題とする*5ことにして、結果を述べると、以下の3つの場合に分けられます。
- かつ
- かつ
- かつ
1つ目の場合は とおくことで
2つ目の場合はフローチャートのとおりです。人が計算できるように(?)分岐していますが、要は 73, 97, 109, 193, 337, 433, 577, 673, 769, 1009, 1153, 3457 で を取ればよいはずです。Julia のツールを使えば以下のようになります。
julia> s = sheet_gen(5, 27, 6, 48); julia> pr(s) mod:(6, 48) candidates: Set([(5, 27)]) julia> pr(s ⊗ prod(73, 97, 109, 193, 337, 433, 577, 673, 769, 1009, 1153, 3457)) mod:(8064, 12096) candidates: Set(Tuple{Int64,Int64}[])
ちゃんと不適になりました。
3つ目の場合も調べましょう。フローチャートによれば 73, 97, 109, 193, 577, 769, 3457 で を取れば不適になるはずです。
julia> s = sheet_gen(5, 9, 6, 12); julia> pr(s) mod:(6, 12) candidates: Set([(5, 9)]) julia> pr(s ⊗ prod(73, 97, 109, 193, 337, 433, 577, 673, 769, 1009, 1153, 3457)) mod:(8064, 12096) candidates: Set([(3425, 969), (1841, 4857), (3749, 12093), (7457, 2697), (1841, 11721), (6449, 11769), (2417, 11721), (5873, 11721), (5, 381), (1841, 6537), (5873, 6537), (6053, 1725), (6881, 7881), (6053, 12093), (5, 10749), (5765, 1725), (5, 12093), (5765, 10749), (6881, 969), (5765, 10365), (5873, 4857), (5, 1725), (3749, 2973), (2417, 11769), (6449, 10041), (6449, 1353), (1841, 10041), (1157, 381), (2849, 7881), (5765, 9021), (2417, 1353), (5765, 381), (1157, 1725), (6449, 11721), (3749, 6429), (7205, 6429), (5873, 10041), (2849, 969), (7457, 969), (6053, 6429), (6053, 4701), (3749, 4701), (7205, 1725), (3749, 1725), (3425, 2697), (3749, 10365), (2417, 10041), (5765, 12093)])
...
あれ???????
実は分岐するところで間違えてます。
julia> s = sheet_gen(5, 9, 6, 12); julia> pr(s) mod:(6, 12) candidates: Set([(5, 9)]) julia> s = s ⊗ sheet_gen(73); julia> pr(s) mod:(18, 12) candidates: Set([(5, 9)]) julia> s = s ⊗ prod(97, 577, 193); julia> pr(s) mod:(288, 48) candidates: Set([(113, 9), (95, 9), (5, 45), (257, 9)])
実は4分岐になっていて、 はありません。
についてはフローチャート通り不適にできます。
julia> s = sheet_gen(95, 9, 288, 48); julia> pr(s) mod:(288, 48) candidates: Set([(95, 9)]) julia> pr(s ⊗ prod(109, 769, 3457)) mod:(1152, 1728) candidates: Set(Tuple{Int64,Int64}[])
他は残念ながらうまく死にません。以下はそのときのSOSのツイートです。
ということで #黒峰問題 は未解決になってしまいました。。。
— すむーずぷりんちゃん🍮 (@mat_der_D) September 16, 2020
だれか (x%288, y%48)=(113, 9), (257, 9), (5, 45) の息の根を止めてください。。。
探索に役に立つツールは以下で公開しているので、適宜使ってくださいhttps://t.co/9tSqGF58W0
ミスを修正しようと試みた話
とはいえここまで頑張ったなら気合でなんとかしたいのが人情。ということで前回一緒に議論してくれただま氏( @dama_math )と DM で議論しました。だま氏の提案で や の があまり大きくならないような素数たちを気合で重ねてみました。(素数の選定はだま氏にやってもらいました!)
julia> s = Sheet((288, 48), Set([(113, 9), (5, 45), (257, 9)])); julia> pr(s) mod:(288, 48) candidates: Set([(113, 9), (5, 45), (257, 9)]) julia> ps = (7, 13, 19, 31, 37, 43, 61, 67, 73, 97, 109, 127, 181, 193, 199, 211, 241, 271, 331, 337, 379, 397, 421, 433, 463, 541, 577, 631, 661, 673, 757, 991, 1009, 1321, 2017, 2113, 2161, 2311, 2377, 2521, 2971, 3169, 3361, 3697, 4159, 4621, 5281, 6337, 7393, 7561, 8317, 8641, 9241, 12097, 13441, 15121, 16633, 18481, 19009, 20161, 23761, 30241, 47521, 55441, 66529, 110881); julia> pr(s ⊗ prod(ps...)) mod:(332640, 332640) candidates: Set([(5, 332637), (5, 166317)])
なぜか のひとつだけしぶとく残っています。実は
まとめ
約一年前に解決宣言した黒峰問題ですが、実は計算ミスがあり解決していませんでした。ツールを使ってたくさん計算して修正を試みましたが、ある解を回避できないために完全解決に至ることができませんでした。誰か解いてください。