問題概略
より大きい最小の素数を であらわす。
これを用いて を次のように定義する。
また,次の式をみたすフィボナッチ数 を用いて と定義する。
を で求めよ。
解説の pdf も作りました。きれいなレイアウトで読みたい方はこちらをどうぞ。
どう解くか
この問題のポイントは「大きな素数を求めること」「大きなフィボナッチ数を求めること」です。これらをうまく処理できる言語にとっては,この問題は「やるだけ」です。
mathematica はこの条件をみたしていて,問題文をほぼそのままコードに落とすだけで解けました。
素数は NextPrime で処理
大きな素数には組み込み関数の NextPrime が使えます。例として を求めてみました。
In[]:= a[n_] := NextPrime[10^14, n]; a[10^5] Out[]= 100000003235443
フィボナッチ数は MatrixPowerMod で処理
フィボナッチ数 は行列を使って求めます。
まず漸化式 を行列のかけ算に直します。
は次のベクトルの第 1 成分です。
この計算を組み込み関数の MatrixPowerMod で処理すると,同時に の計算もできて楽です。この関数は現行のマニュアルには載っていませんが,何の問題もなく使えます。StackExchange を参考にしました。
の をとったものを とおいて,次のように書きました。
modnum = 1234567891011; mat = {{1, 1}, {1, 0}}; c[n_] := Mod[First@#, modnum] &@ (Algebra`MatrixPowerMod[mat, a@n - 1, modnum] . {1, 0});
実装
並列処理
上にあげたコードを使って計算したらちょっと遅かったので並列処理しました。計算時間は約 200 秒です。
nmax = 10^5; a[n_] := NextPrime[10^14, n]; modnum = 1234567891011; mat = {{1, 1}, {1, 0}}; c[n_] := Mod[First@#, modnum] &@ (Algebra`MatrixPowerMod[mat, a@n - 1, modnum] . {1, 0}); ans = Mod[Total[ParallelMap[c, Range@nmax]], modnum]
前の項を使い回す
もう少し速くしたかったので, を毎回一から計算するかわりに を再利用する方法を試してみました。
\begin{align*}
\begin{pmatrix}
b_n\\ \square
\end{pmatrix}
=\begin{pmatrix}
1&1\\1&0
\end{pmatrix}^{a_n-1}
\begin{pmatrix}
1\\0
\end{pmatrix}\quad \therefore
\begin{pmatrix}
b_n\\ \square
\end{pmatrix}
=\begin{pmatrix}
1&1\\1&0
\end{pmatrix}^{a_n-a_{n-1}}
\begin{pmatrix}
b_{n-1}\\ \square
\end{pmatrix}
\end{align*}
計算回数はかなり減りますが,この方法だと並列化できないのでかなり遅くなります。
までの和をとる時間を比較しました。元のコードが約 4 秒で,新しく書いたコードは約 12 秒です。
結論として,mathematica では「できるだけ組み込み関数を使って,並列化する」のが最適解だと思います。
nmax = 10^3; a[n_] := a[n] = NextPrime[10^14, n]; modnum = 1234567891011; mat = {{1, 1}, {1, 0}}; v[1] = Algebra`MatrixPowerMod[mat, a@1 - 1, modnum] . {1, 0}; v[n_] := v[n] = Mod[#, modnum] &@ (Algebra`MatrixPowerMod[mat, a[n] - a[n - 1], modnum] . v[n - 1]); ans = First@Mod[Total[v /@ Range@nmax], modnum]