問題概略
入力の 2 進真理値表は 個の入力ビット(2 進数で 0 は「偽」,1 は「真」)から 1 個の出力ビットへの写像である。
たとえば論理和(AND)と排他的論理和(XOR)の 2 入力真理値表は次のようになる。6 ビットの入力(, , , , , )に対して以下の式をみたす 6 入力の 2 進真理値表 はいくつあるか。
解説の pdf も作りました。きれいなレイアウトで読みたい方はこちらをどうぞ。
何を求めればいい?
左右の の引数を , で表して AND と XOR をそれぞれ , で表すと,与式は次のようになります。
\begin{align*}
&\tau(x)\land \tau\left(p(x)\right)=0\\
&p(a,\, b,\, c,\, d,\, e,\, f)=(b,\, c,\, d,\, e,\, f,\, a\oplus (b\land c))
\end{align*}
以下,真偽を で表します。
与式が成立するのは と が「両方とも 」または「片方が ,もう片方が 」のときです。
この条件をみたすように , ……を決めるときのルールは次のようになります。
- 「と が両方とも 」のとき は でも でもいい
- 「 が , が 」のとき は
- 「 が , が 」のとき は でも でもいい
- 「 と が両方とも 」になる は存在しない
入試数学でよくある「 は連続してもいいが, は連続しない順列」の問題を連想しますね。
などがとる値は全部で 個なので, の連鎖は循環しているはずです。この循環を円で表すと,本問は
「円周上に が連続しないようにうまく を置く方法が何通りあるか」
を求める問題です。
mathematica で実験
64 個の すべてが 1 つの円周上に並ぶとは限らないので,まずこれを mathematica で調べました。
と を dsu の同じ集合に入れて,部分集合の要素数を数えました。
小さい方から順に 1, 2, 3, 6, 6, 46 個でした。
In[]:= AbsoluteTiming[ p[{a_, b_, c_, d_, e_, f_}] := {b, c, d, e, f, BitXor[a, BitAnd[b, c]]}; ds = CreateDataStructure["DisjointSet"]; For[i = 0, i < 2^6, i++, x = IntegerDigits[i, 2, 6]; y = p@x; ds["Insert", x]; ds["Insert", y]; ds["Unify", x, y]]; Sort[Length /@ ds["Subsets"]]] Out[]= {0.0005681, {1, 2, 3, 6, 6, 46}}
と の間に有向辺をはってグラフにすると視覚的にわかりやすいです。
p[{a_, b_, c_, d_, e_, f_}] := {b, c, d, e, f, BitXor[a, BitAnd[b, c]]}; lst = Tuples[{0, 1}, 6]; f[lst_] := lst -> p@lst; Graph[f /@ lst]
実はリュカ数列
これまでの考察をもとに,条件をみたす真理値表の埋め方が何通りあるか計算します。
要素数 1 の集合に属する は で自分自身にうつる元なので の 1 通りです。
要素数 2 の集合は「両方とも 」か「片方が ,もう片方が 」なので 通り。
要素数 3 の集合に含まれる は高々 1 個。0 個のものが 1 通りで,1 個のものが 3 通りの計 4 通り。
要素数 6 の集合も同様の casework で求められますが,要素数 46 個の集合は無理なので一般化して要素数 個の集合を考えます。
一番若い数字の元が か かで場合分けします。
- のとき,その両隣は で残り 個の元の は任意
- のとき,残り 個の元の は任意
「 は連続してもいいが, は連続しない」をみたすように円周上に 個の を並べる方法が 通りあり,直線上に並べる方法が 通りあるとします。上の場合分けより
がフィボナッチ型の漸化式をみたすことは入試数学ではよく知られた事実です。
詳細は割愛しますが,最初の 1 個が か かで場合分けすると次の式が導けます。
(1)(2)から もフィボナッチ型の漸化式をみたすことがわかります。
\begin{align*}
a_n+a_{n+1} &=(b_{n-1}+b_{n-3})+(b_{n}+b_{n-2})\\
&=(b_{n-1}+b_{n})+(b_{n-3}+b_{n-2})\\
&=b_{n+1}+b_{n-1}\\
&=a_{n+2}
\end{align*}
要素数 1, 2 の集合のところで求めたように , です。
これらを使って, を求めれば解けますが,実はもうひと工夫できます。
, , をみたす はリュカ数列 です。
実装
mathematica
組み込み関数を使って を求めると速いです。約0.0005秒でした。*1
In[]:= AbsoluteTiming[ p[{a_, b_, c_, d_, e_, f_}] := {b, c, d, e, f, BitXor[a, BitAnd[b, c]]}; ds = CreateDataStructure["DisjointSet"]; For[i = 0, i < 2^6, i++, x = IntegerDigits[i, 2, 6]; y = p@x; ds["Insert", x]; ds["Insert", y]; ds["Unify", x, y]]; ans = Apply[Times, LucasL[Length@#] & /@ ds["Subsets"]];] Out[]= {0.0004947, NUll}
python
python でも解きました。計算時間は約 0.001 秒で,mathematica より遅いという意外な結果でした。
dsu は ACL(AtCoder Library) の python 移植版を使いました。
github.com
import time from atcoder.dsu import DSU from sympy import lucas from math import prod def p(n): a, b, c, d, e, f = [int(x) for x in format(n, '06b')] lst = [b, c, d, e, f, a ^ (b & c)] return int(''.join(map(str, lst)), 2) def solve(): g = DSU(2 ** 6) for x in range(0, 2 ** 6): g.merge(x, p(x)) return prod(map(lambda x: lucas(len(x)), g.groups())) if __name__ == '__main__': start = time.time() print(solve()) elapsed_time = time.time() - start print("elapsed_time: {0} [sec]".format(elapsed_time))
*1:101問以降なので答えの数値は省略。