ミステリ小説『笑わない数学者』(森博嗣・著)を読了。『すべてがFになる』から続くシリーズの三作目です。
 なかなか面白いトリックでした。トリックの予想はついたのですが、書かれている文章の中から証拠を見つけることはできませんでした。結末を知ってからあとから見返して見るとヒントになりそうなことは書いてありました。
 世間では予想が的中しただけで「トリックが分かった!」と言ってしまう人がいるかもしれませんが、やはり証拠まで見つけてから「よし、わかった!」と言いたいものです。

 それはさておき、この小説で出題されているビリヤードの問題についてです。
 なんとこの問題、作中に答えが書かれていません!
 これは是非とも自力で解きたいので、pythonでプログラムを書いてコンピュータで解くことにしました(そうやって解いたのを自力で解いたと言って良いのかは脇に置いて…)。

 その問題ですが、語弊があるといけないので作中からそのまま引用させていただきます。

「さて、では、もう一つ問題を出そう。五つのビリヤードの玉を、真珠のネックレスのように、リングにつなげてみるとしよう。玉には、それぞれナンバが書かれている。さて、この五つの玉のうち、幾つ取っても良いが、隣どうし連続したものしか取れないとしよう。一つでも、二つでも、五つ全部でも良い。しかし、離れているものは取れない。この条件で取った玉のナンバを足し合わせて、1から 21 までのすべての数ができるようにしたい。さあ、どのナンバの玉を、どのように並べて、ネックレスを作れば良いかな?」

 『ナンバ』と書いてあるのはコピペミスではなく引用元のままです。どうやら森博嗣作品では昔のJISのガイドラインに則って書かれているようです。外来語をカタカナで表記する場合、3音以上の用語は末尾の長音符号を省くというルールがあります(ありました)。例えばメモリーはメモリ、コンピューターはコンピュータといった感じです。ただし、このJISガイドラインは昔のもので、今では長音符号を付けても良いとされています。
 要するに『ナンバ』はナンバーのことです。

 閑話休題。問題では下の図のように数珠繋ぎにし、連続する任意の個数を取り出したときの合計値について問うています。
 ビリヤード問題イメージ

 取り出せるパターンは、
 玉1個が5パターン、
 玉2個が5パターン、
 玉3個が5パターン、
 玉4個が5パターン、
 玉5個が1パターン、
 で、全部で21パターン。
 『1から 21 までのすべての数ができるようにしたい』ということなので、21パターンでちょうど21種類の数を作るということになります。21パターンで21種類なので同じ数が重複することはありません。

 色々考えて分かったことを列挙すると、
 ・作れるパターンは21パターンなので1~21までの重複は一切ない。全て1パターンずつになる。
 ・1を作るためにはナンバ1の玉が必ず必要となる。上の図のAは1で固定してしまう。
 ・全ての玉を合計した値が最大値でありそれは21となる。これだけである程度のふるいがけができる。
 ・玉に書かれているナンバの最大値は11。玉4個で作れる最小値は1+2+3+4=10で、残り1個では合計を21にしようとすると残り1個の最大は11となる(もし11より大きい数字を使うと合計値は必ず21を超える)。

 ということで雑に作ったpythonのコードは下の通り。
 itertools.combinationsで4個の玉をチョイス(それにナンバ1をプラスする)。合計が21でないものは弾いてふるいがけ。ふるいを通ったものをitertools.permutationsで並び変えて適合するものを探す、という感じです。

#coding:utf-8

import time
import itertools

def test():
    tama = [2,3,4,5,6,7,8,9,10,11]
    A = 1
    for lst in itertools.combinations(tama, 4):
        if A + sum(lst) != 21: continue
        for B, C, D, E in itertools.permutations(lst):
            s = [0] * 21
            s[A+B+C+D+E-1] += 1
            s[A-1] += 1
            s[B-1] += 1
            s[C-1] += 1
            s[D-1] += 1
            s[E-1] += 1
            s[A+B-1] += 1
            s[B+C-1] += 1
            s[C+D-1] += 1
            s[D+E-1] += 1
            s[E+A-1] += 1
            s[A+B+C-1] += 1
            s[B+C+D-1] += 1
            s[C+D+E-1] += 1
            s[D+E+A-1] += 1
            s[E+A+B-1] += 1
            s[A+B+C+D-1] += 1
            s[B+C+D+E-1] += 1
            s[C+D+E+A-1] += 1
            s[D+E+A+B-1] += 1
            s[E+A+B+C-1] += 1
            if 0 not in s:
                print("{},{}, {}, {}, {}".format(A, B, C, D, E))

if __name__ == '__main__':
    start_time = time.time()
    print("start time: {}".format(time.ctime(start_time)))
    test()
    elapsed_time = time.time() - start_time
    print("elapsed time:{:.3f}sec".format(elapsed_time))

 上を実行した結果はこれ。
 1,3, 10, 2, 5
 1,5, 2, 10, 3

 答えは2つありますが、左右対称になっているので。答えは1つということになります。

 実行時間も計測していますがたった0.0001秒でした。5個ではあっという間に終わります。
 しかし玉の数を増やしていくと、
 玉9個で1~73の数を探すのに2秒、
 玉10個で1~91の数を探すのに73秒、
 玉11個で1~111の数を探すのに48分、
 と1個増えるごとに約40倍になっていきます。
 12個だと32時間は掛かる見込みなのでここで断念しました。


 インターネットの黎明期に発売されたミステリ小説ですが、昔の人はこれの答えを知るためにどうしたんでしょうね?