1 から 9 までの数字を順番に並べ、間に + と - を補って値が N になる式を作ってください。 ただし、1 の先頭に - 符号は付けないことにします。
ABCDE÷FGHI=15
ABC26÷DEFG=26
次に示す式を完成させてください。□にはどんな数字を入れてもかまいません。
(A) □□□□□×□□□□□=123456789 (B) □□□□×□□□□□=123456789 (C) □□□□×□□□□□□=987654321 (D) □□□×□□□□□□□=987654321
(A) は参考文献『ブルーバックス B-1377 超々難問数理パズル 解けるものなら解いてごらん』に掲載されています。ほかに面白い式はないかプログラムを作って探してみたところ、(B), (C), (D) を見つけました。解があることは保証しますが、手で解くにはちょっと難しいかもしれません。興味のある方は挑戦してみてください。
リスト : 小町算(2) from itertools import permutations # 式の表示 def print_expr(expr, ans): for x in expr: print(x, end=' ') print(f'= {ans}') # 式の計算 def calc_expr(expr): n = expr[0] for i in range(1, len(expr), 2): if expr[i] == '+': n += expr[i + 1] else: n -= expr[i + 1] return n # 式の生成 def make_expr(n, expr, ans): if n == 10: if calc_expr(expr) == ans: print_expr(expr, ans) else: make_expr(n + 1, expr + ['+', n], ans) make_expr(n + 1, expr + ['-', n], ans) m = expr[-1] expr[-1] = m * 10 + n make_expr(n + 1, expr, ans) expr[-1] = m # 小町算の解法 def komachi_solver(ans): make_expr(2, [1], ans) # 小町虫食い算 def komachi21(): for xs in permutations('123456789'): n = int(''.join(xs[:5])) m = int(''.join(xs[5:])) if n % m == 0 and n // m == 15: print(f'{n} / {m} = 15') def komachi22(): for xs in permutations('1345789'): n = int(''.join(xs[:3])) * 100 + 26 m = int(''.join(xs[3:])) if n % m == 0 and n // m == 26: print(f'{n} / {m} = 26') # 小町数 def komachi3a(): for n in range(10000, 100000): if 123456789 % n == 0: m = 123456789 // n if m >= 10000 and n < m: print(f'{n} * {m} = 123456789') def komachi3b(): for n in range(1000, 10000): if 123456789 % n == 0: m = 123456789 // n print(f'{n} * {m} = 123456789') def komachi3c(): for n in range(1000, 10000): if 987654321 % n == 0: m = 987654321 // n print(f'{n} * {m} = 987654321') def komachi3d(): for n in range(100, 1000): if 987654321 % n == 0: m = 987654321 // n print(f'{n} * {m} = 987654321')
1 + 2 + 34 - 5 - 6 - 7 - 8 - 9 1 + 23 + 4 + 56 + 7 - 89 1 - 2 + 34 + 56 - 78 - 9 12 + 3 + 4 + 5 + 67 - 89 12 + 3 + 4 - 5 - 6 - 7 - 8 + 9 12 + 3 - 4 + 5 - 6 - 7 + 8 - 9 12 + 3 - 4 - 5 + 6 + 7 - 8 - 9 12 - 3 + 4 + 5 - 6 + 7 - 8 - 9 12 - 3 - 4 - 5 - 6 + 7 - 8 + 9 123 + 4 - 56 - 78 + 9 123 - 45 + 6 + 7 - 89
1 + 2 + 34 - 5 + 6 - 7 - 8 - 9 1 + 2 - 34 - 5 + 67 - 8 - 9 1 - 2 + 34 - 5 - 6 - 7 + 8 - 9 1 - 2 - 3 - 45 - 6 + 78 - 9 1 - 23 + 4 + 56 - 7 - 8 - 9 1 - 23 - 4 - 56 + 7 + 89 1 - 23 - 45 - 6 + 78 + 9 12 + 3 + 4 - 5 + 6 - 7 - 8 + 9 12 + 3 + 4 - 5 - 6 + 7 + 8 - 9 12 + 3 - 4 + 5 + 6 - 7 + 8 - 9 12 + 34 - 56 + 7 + 8 + 9 12 - 3 + 4 + 5 + 6 + 7 - 8 - 9 12 - 3 - 4 + 5 - 6 - 7 + 8 + 9 12 - 3 - 4 - 5 + 6 + 7 - 8 + 9 12 - 3 - 45 + 67 - 8 - 9
1 + 2 + 3 + 4 + 5 + 6 - 7 - 8 + 9 1 + 2 + 3 + 4 + 5 - 6 + 7 + 8 - 9 1 + 2 + 3 - 4 - 5 - 6 + 7 + 8 + 9 1 + 2 + 3 - 4 - 56 + 78 - 9 1 + 2 - 3 + 4 - 5 + 6 - 7 + 8 + 9 1 + 2 - 3 + 45 - 6 - 7 - 8 - 9 1 + 2 - 3 - 4 + 5 + 6 + 7 - 8 + 9 1 + 2 - 34 + 56 + 7 - 8 - 9 1 + 23 + 4 + 5 + 6 - 7 - 8 - 9 1 + 23 + 4 + 56 - 78 + 9 1 + 23 - 4 - 5 + 6 - 7 - 8 + 9 1 + 23 - 4 - 5 - 6 + 7 + 8 - 9 1 - 2 + 3 + 4 + 5 - 6 - 7 + 8 + 9 1 - 2 + 3 + 4 - 5 + 6 + 7 - 8 + 9 1 - 2 + 3 - 4 + 5 + 6 + 7 + 8 - 9 1 - 2 + 3 - 4 - 5 - 67 + 89 1 - 2 - 3 - 4 + 5 - 6 + 7 + 8 + 9 1 - 2 - 34 + 56 - 7 - 8 + 9 12 + 34 + 56 - 78 - 9 123 -45 + 6 - 78 + 9
1 + 2 + 34 - 5 - 6 + 7 - 8 - 9 1 - 2 + 34 + 5 + 67 - 89 1 - 2 + 34 - 5 - 6 - 7 - 8 + 9 12 + 3 + 4 - 5 - 6 + 7 - 8 + 9 12 + 3 - 4 + 5 + 6 - 7 - 8 + 9 12 + 3 - 4 + 5 - 6 + 7 + 8 - 9 12 - 3 + 4 + 5 + 6 - 7 + 8 - 9 12 - 3 - 4 - 5 + 6 - 7 + 8 + 9
1 + 2 + 3 + 4 + 5 - 6 + 7 - 8 + 9 1 + 2 + 3 + 4 - 5 + 6 + 7 + 8 - 9 1 + 2 - 3 + 4 - 5 - 6 + 7 + 8 + 9 1 + 2 - 3 + 4 - 56 + 78 - 9 1 + 2 - 3 - 4 + 5 + 6 - 7 + 8 + 9 1 + 2 - 34 + 56 - 7 + 8 - 9 1 + 23 + 4 + 5 - 6 + 7 - 8 - 9 1 + 23 - 4 - 5 - 6 + 7 - 8 + 9 1 - 2 + 3 + 4 - 5 + 6 - 7 + 8 + 9 1 - 2 + 3 + 45 - 6 - 7 - 8 - 9 1 - 2 + 3 - 4 + 5 + 6 + 7 - 8 + 9 1 - 2 - 3 + 4 + 5 + 6 + 7 + 8 - 9 1 - 2 - 3 + 4 - 5 - 67 + 89 1 - 2 - 3 - 4 - 5 + 6 + 7 + 8 +9 1 - 2 - 3 - 45 + 67 + 8 - 9 1 - 23 + 4 + 5 + 6 + 7 + 8 + 9 1 - 23 - 45 + 67 + 8 + 9
1 + 2 + 34 - 5 - 6 - 7 + 8 - 9 1 + 2 - 3 - 45 - 6 + 78 - 9 1 - 2 - 3 - 4 - 56 - 7 + 89 12 + 3 + 4 - 5 - 6 - 7 + 8 + 9 12 + 3 - 4 + 5 - 6 + 7 - 8 + 9 12 + 3 - 4 - 5 + 6 + 7 + 8 - 9 12 - 3 + 4 + 5 + 6 - 7 - 8 + 9 12 - 3 + 4 + 5 - 6 + 7 + 8 - 9 12 - 3 - 4 - 5 - 6 + 7 + 8 + 9 12 - 3 - 4 - 56 + 78 - 9 12 - 34 - 56 + 7 + 89
1 + 2 + 34 + 5 + 67 - 89 1 + 2 + 34 - 5 - 6 - 7 - 8 + 9 1 - 2 + 3 - 45 - 6 + 78 - 9 1 - 2 + 34 + 5 + 6 - 7 - 8 - 9 1 - 2 + 34 + 56 - 78 + 9 1 - 2 - 34 + 5 + 67 - 8 - 9 1 - 23 - 4 + 56 + 7 - 8 - 9 12 + 3 + 4 + 5 + 6 + 7 - 8 - 9 12 + 3 - 4 + 5 - 6 - 7 + 8 + 9 12 + 3 - 4 - 5 + 6 + 7 - 8 + 9 12 + 3 - 45 + 67 - 8 - 9 12 + 34 + 56 + 7 - 89 12 - 3 + 4 + 5 - 6 + 7 - 8 + 9 12 - 3 + 4 - 5 + 6 + 7 + 8 - 9 123 + 4 - 5 - 6 - 7 - 89
1 + 234 - 5 - 6 - 7 - 8 - 9 123 + 4 + 5 + 67 - 8 + 9 123 - 4 + 5 - 6 - 7 + 89
27945÷1863=15 92745÷6183=15
89726÷3451=26
(A) 10821×11409=123456789 (B) 3607×34227=123456789 3803×32463=123456789 (C) 2601×379721=987654321 (D) 153×6455257=987654321 289×3417489=987654321 867×1139163=987654321
n 個の整数 0, 1, ..., n - 1 の順列を考えます。このとき、i 番目の要素が整数 i ではない順列を「完全順列 (derangement)」といいます。たとえば、0, 1, 2 の完全順列は次の 2 通りになります。
1, 2, 0 2, 0, 1
整数 0 は 0 番目に、1 は 1 番目に、 2 は 2 番目に現れていません。これが完全順列になります。そして、完全順列の総数を「モンモール数 (Montmort number)」といいます。モンモール数は次の漸化式で求めることができます。
それでは問題です。次の関数を Python で作ってください。
リスト : 完全順列 def derangement(fn, n, ps = []): if len(ps) == n: fn(ps[:]) else: for x in range(n): if x == len(ps) or x in ps: continue ps.append(x) derangement(fn, n, ps) ps.pop()
>>> derangement(print, 3) [1, 2, 0] [2, 0, 1] >>> derangement(print, 4) [1, 0, 3, 2] [1, 2, 3, 0] [1, 3, 0, 2] [2, 0, 3, 1] [2, 3, 0, 1] [2, 3, 1, 0] [3, 0, 1, 2] [3, 2, 0, 1] [3, 2, 1, 0]
derangement() は順列を生成するプログラムに条件を追加するだけです。数字 x を選択するとき、その値が位置 len(ps) と等しくないことを確認するだけです。
リスト : モンモール数 def montmort(n): if n == 1: return 0 elif n == 2: return 1 else: return (n - 1) * (montmort(n - 1) + montmort(n - 2)) # 別解 def montmort_loop(n): a, b = 0, 1 for i in range(1, n): a, b = b, (i + 1) * (a + b)
>>> for x in range(1, 11): ... print(montmort(x), montmort_loop(x)) ... 0 0 1 1 2 2 9 9 44 44 265 265 1854 1854 14833 14833 133496 133496 1334961 1334961 >>> montmort_loop(20) 895014631192902121 >>> montmort_loop(30) 97581073836835777732377428235481 >>> montmort_loop(40) 300158458444475693321518926221316715906770469041 >>> montmort_loop(50) 11188719610782480504630258070757734324011354208865721592720336801
関数 montmort() は公式をそのままプログラムしただけです。二重再帰になっているので、実行速度はとても遅くなります。これを繰り返しに変換すると別解のようになります。考え方はフィボナッチ数列と同じです。累積変数 a に i 番目の値を、b に i + 1 番目の値を保存しておきます。すると、i + 2 番目の値は (i + 1) * (a + b)) で計算することができます。あとは、b の値を a に、新しい値を b にセットして処理を繰り返すだけです。
Python のリストで表した集合 xs を分割することを考えます。たとえば、集合 [1, 2, 3] は次のように分割することができます。
1 分割 : [[1, 2, 3]] 2 分割 : [[1, 2], [3]], [[1, 3], [2]], [[1], [2, 3]] 3 分割 : [[1], [2], [3]]
このように、分割した集合 ys は元の集合 xs の部分集合になります。分割した部分集合の積は空集合になり、分割した部分集合のすべての和を求めると元の集合になります。そして、集合を分割する方法の総数を「ベル数 (Bell Number)」といいます。ベル数は次の漸化式で求めることができます。
それでは問題です。次の関数を Python で作ってください。
集合を分割するアルゴリズムは簡単です。たとえば、n -1 個の要素 x1, ..., xn-1 を持つ集合を分割したところ、i 個の部分集合 S1, ..., Si が生成されたとしましょう。ここに、n 番目の要素 xn を追加すると、要素が n 個の集合を分割することができます。
新しい要素を追加する場合は次に示す手順で行います。
簡単な例を示しましょう。次の図を見てください。
[] ─ [[1]] ─┬─ [[1, 2]] ─┬─ [[1, 2, 3]] │ │ │ └─ [[1, 2], [3]] │ └─ [[1], [2]] ─┬─ [[1, 3] [2]] │ ├─ [[1,], [2, 3]] │ └─ [[1], [2], [3]] 図 : 集合 [1, 2, 3] を分割する
部分集合を格納する配列を用意します。最初、部分集合は空集合なので空の配列に初期化します。次に、要素 1 を追加します。部分集合は空なので、手順 1 は適用できません。手順 2 を適用して新しい部分集合 [1] を追加します。
次に要素 2 を追加します。[[1]] に 手順 1 を適用すると、部分集合 [1] に要素を追加して [[1, 2]] になります。手順 2 を適用すると、新しい部分集合 [2] を追加して [[1], [2]] になります。最後に 3 を追加します。[[1, 2]] に手順 1 を適用すると [[1, 2, 3]] に、手順 2 を適用すると [[1, 2], [3]] になります。[[1], [2]] に手順 1 を適用すると [[1, 3] [2]] と [[1], [2, 3]] になり、手順 2 を適用すると [[1], [2], [3]] になります。
このように、簡単な方法で集合を分割することができます。実際にプログラムを作る場合、上図を木と考えて、深さ優先で木をたどると簡単です。次のリストを見てください。
リスト : 集合の分割 def partition_of_set(fn, xs, j = 0, a = []): if len(xs) == j: fn(a) else: for i in range(len(a)): a[i].append(xs[j]) partition_of_set(fn, xs, j + 1, a) a[i].pop() a.append([xs[j]]) partition_of_set(fn, xs, j + 1, a) a.pop()
関数 partition_of_set() の引数 xs が集合、引数 j が追加する要素の添字、生成した部分集合は累積変数 a に格納します。j が len(xs) と等しい場合、追加する要素がなくなったので fn(a) を評価します。要素がある場合、i 番目の部分集合に要素 ls[j] を追加して、partition_of_set() を再帰呼び出しします。すべての部分集合に要素を追加したら、xs[j] を要素として持つ部分集合を生成して累積変数 a に追加します。
簡単な実行例を示します。
>>> partition_of_set(print, [1,2,3]) [[1, 2, 3]] [[1, 2], [3]] [[1, 3], [2]] [[1], [2, 3]] [[1], [2], [3]] >>> partition_of_set(print, [1,2,3,4]) [[1, 2, 3, 4]] [[1, 2, 3], [4]] [[1, 2, 4], [3]] [[1, 2], [3, 4]] [[1, 2], [3], [4]] [[1, 3, 4], [2]] [[1, 3], [2, 4]] [[1, 3], [2], [4]] [[1, 4], [2, 3]] [[1], [2, 3, 4]] [[1], [2, 3], [4]] [[1, 4], [2], [3]] [[1], [2, 4], [3]] [[1], [2], [3, 4]] [[1], [2], [3], [4]]
リスト : ベル数 # 組み合わせの数 def comb(n, r): if n == r or r == 0: return 1 return comb(n, r - 1) * (n - r + 1) // r # ベル数 def bell_number(n): bs = [1] for i in range(n): a = 0 for k, b in enumerate(bs): a += comb(i, k) * b bs.append(a) return bs[-1]
関数 bell_number() は公式をそのままプログラムするだけです。累積変数 bs にベル数を格納します。nCk は関数 comb() で求めます。nCk * B(k) の総和は 2 番目の for ループで計算します。最後に、bs の末尾要素を return で返します。
簡単な実行例を示します。
>>> for x in range(11): ... print(bell_number(x)) ... 1 1 2 5 15 52 203 877 4140 21147 115975 >>> bell_number(20) 51724158235372 >>> bell_number(30) 846749014511809332450147 >>> bell_number(40) 157450588391204931289324344702531067 >>> bell_number(50) 185724268771078270438257767181908917499221852770
リスト : 集合のグループ分け def group_partition(fn, xs, n, m, j = 0, a = []): if len(xs) == j: fn(a) else: for i in range(len(a)): if len(a[i]) >= n: continue a[i].append(xs[j]) group_partition(fn, xs, n, m, j + 1, a) a[i].pop() if len(a) < m: a.append([xs[j]]) group_partition(fn, xs, n, m, j + 1, a) a.pop()
group_partition() は partition_of_set() を改造するだけで簡単に作成することができます。生成する部分集合の大きさを n に、部分集合の個数を m に制限するだけです。i 番目の部分集合に要素を追加する場合、len(a[i]) が n 未満であることをチェックします。新しい部分集合を追加する場合、len(a) が m 未満であることをチェックします。これで集合をグループに分けることができます。
簡単な実行例を示します。
>>> group_partition(print, [1,2,3,4], 2, 2) [[1, 2], [3, 4]] [[1, 3], [2, 4]] [[1, 4], [2, 3]] >>> group_partition(print, [1,2,3,4,5,6], 2, 3) [[1, 2], [3, 4], [5, 6]] [[1, 2], [3, 5], [4, 6]] [[1, 2], [3, 6], [4, 5]] [[1, 3], [2, 4], [5, 6]] [[1, 3], [2, 5], [4, 6]] [[1, 3], [2, 6], [4, 5]] [[1, 4], [2, 3], [5, 6]] [[1, 5], [2, 3], [4, 6]] [[1, 6], [2, 3], [4, 5]] [[1, 4], [2, 5], [3, 6]] [[1, 4], [2, 6], [3, 5]] [[1, 5], [2, 4], [3, 6]] [[1, 6], [2, 4], [3, 5]] [[1, 5], [2, 6], [3, 4]] [[1, 6], [2, 5], [3, 4]] >>> group_partition(print, [1,2,3,4,5,6], 3, 2) [[1, 2, 3], [4, 5, 6]] [[1, 2, 4], [3, 5, 6]] [[1, 2, 5], [3, 4, 6]] [[1, 2, 6], [3, 4, 5]] [[1, 3, 4], [2, 5, 6]] [[1, 3, 5], [2, 4, 6]] [[1, 3, 6], [2, 4, 5]] [[1, 4, 5], [2, 3, 6]] [[1, 4, 6], [2, 3, 5]] [[1, 5, 6], [2, 3, 4]]
グループ分けの総数は次の式で求めることができます。
たとえば、n = 3, m = 5 の場合は次のようになります。
これをそのままプログラムすると次のようになります。
リスト : グループ分けの総数 # 階乗 def fact(n): if n == 0: return 1 return n * fact(n - 1) # グループ分けの総数 def group_partition_number(n, m): a = 1 for k in range(n * m, 0, -n): a *= comb(k, n) return a // fact(m)
階乗は関数 fact() で、組み合わせの個数は関数 comb() で計算します。要素の個数を変数 k にセットし、累積変数 a に comb(k, n) を乗算します。あとは k から n を減算し、k が 0 でなければ処理を繰り返すだけです。最後に a // fact(m) を計算して返します。
簡単な実行例を示します。
>>> group_partition_number(2, 2) 3 >>> group_partition_number(2, 3) 15 >>> group_partition_number(3, 2) 10 >>> group_partition_number(3, 3) 280 >>> group_partition_number(3, 4) 15400 >>> group_partition_number(3, 5) 1401400
# # number2.py : 数字のパズル (2) # # Copyright (C) 2022 Makoto Hiroi # from itertools import permutations # 小町算 # 式の表示 def print_expr(expr, ans): for x in expr: print(x, end=' ') print(f'= {ans}') # 式の計算 def calc_expr(expr): n = expr[0] for i in range(1, len(expr), 2): if expr[i] == '+': n += expr[i + 1] else: n -= expr[i + 1] return n # 式の生成 def make_expr(n, expr, ans): if n == 10: if calc_expr(expr) == ans: print_expr(expr, ans) else: make_expr(n + 1, expr + ['+', n], ans) make_expr(n + 1, expr + ['-', n], ans) m = expr[-1] expr[-1] = m * 10 + n make_expr(n + 1, expr, ans) expr[-1] = m # 小町算の解法 def komachi_solver(ans): make_expr(2, [1], ans) # 小町虫食い算 def komachi21(): for xs in permutations('123456789'): n = int(''.join(xs[:5])) m = int(''.join(xs[5:])) if n % m == 0 and n // m == 15: print(f'{n} / {m} = 15') def komachi22(): for xs in permutations('1345789'): n = int(''.join(xs[:3])) * 100 + 26 m = int(''.join(xs[3:])) if n % m == 0 and n // m == 26: print(f'{n} / {m} = 26') # 小町数 def komachi3a(): for n in range(10000, 100000): if 123456789 % n == 0: m = 123456789 // n if m >= 10000 and n < m: print(f'{n} * {m} = 123456789') def komachi3b(): for n in range(1000, 10000): if 123456789 % n == 0: m = 123456789 // n print(f'{n} * {m} = 123456789') def komachi3c(): for n in range(1000, 10000): if 987654321 % n == 0: m = 987654321 // n print(f'{n} * {m} = 987654321') def komachi3d(): for n in range(100, 1000): if 987654321 % n == 0: m = 987654321 // n print(f'{n} * {m} = 987654321') # 完全順列 def derangement(fn, n, ps = []): if len(ps) == n: fn(ps[:]) else: for x in range(n): if x == len(ps) or x in ps: continue ps.append(x) derangement(fn, n, ps) ps.pop() # モンモール数 def montmort(n): if n == 1: return 0 elif n == 2: return 1 else: return (n - 1) * (montmort(n - 1) + montmort(n - 2)) def montmort_loop(n): a, b = 0, 1 for i in range(1, n): a, b = b, (i + 1) * (a + b) return a # 集合の分割 def partition_of_set(fn, xs, j = 0, a = []): if len(xs) == j: fn(a) else: for i in range(len(a)): a[i].append(xs[j]) partition_of_set(fn, xs, j + 1, a) a[i].pop() a.append([xs[j]]) partition_of_set(fn, xs, j + 1, a) a.pop() # 組み合わせの数 def comb(n, r): if n == r or r == 0: return 1 return comb(n, r - 1) * (n - r + 1) // r # ベル数 def bell_number(n): bs = [1] for i in range(n): a = 0 for k, b in enumerate(bs): a += comb(i, k) * b bs.append(a) return bs[-1] # 集合のグループ分け def group_partition(fn, xs, n, m, j = 0, a = []): if len(xs) == j: fn(a) else: for i in range(len(a)): if len(a[i]) >= n: continue a[i].append(xs[j]) group_partition(fn, xs, n, m, j + 1, a) a[i].pop() if len(a) < m: a.append([xs[j]]) group_partition(fn, xs, n, m, j + 1, a) a.pop() # 階乗 def fact(n): if n == 0: return 1 return n * fact(n - 1) # グループ分けの総数 def group_partition_number(n, m): a = 1 for k in range(n * m, 0, -n): a *= comb(k, n) return a // fact(m)