のように指数を連ねた表記を一般に 指数タワー といいます。また、指数タワーが表す値を 指数タワーの値 と呼ぶことにします。ここで、冪乗の演算は右結合、即ち、積みあがった指数の上側から計算することに注意が必要です。例えば、 となります。
本稿では、次の問題の解法について説明します。
指数タワー の値を で割った余りを求めよ。
制約
記法
- を正の整数全体の集合とします。
- を整数全体の集合とします。
- 非負整数 、正の整数 に対して、 で を で割ったときの余りを表します。
基本事項
解説をする際に使用する定理 (定義) をまとめました。
解説で登場した際は、こちらを参照していただければと思います。
が成り立つ。
本題
指数タワー の値を で割った余りを求めよ。
制約
となる整数 が存在した場合、そのような整数 のうち最小のものを として 番目以降の項を全て取り去ってしまっても答えは変わりません (任意の に対して であるため)。この操作を行うことによって数列が空となった場合、すなわち であった場合、答えは明らかに です。そうでない場合、数列の全ての項が 以上の整数になります。 結果として次の問題に帰着されるので、以下この問題について考えます。
指数タワー の値を で割った余りを求めよ。
制約
さて、関数 を以下によって定めます。
求める答えは となります。
関数 の定め方から、
と表せます。 のとき、そのままでは指数の が非常に大きくなり得るので、うまく剰余をとるなどして、(答えは変えないまま) 指数の値を小さくしたいです。
ここで一旦、 を固定して、任意の に対して と が互いに素である と仮定してみましょう。
とすると、 と は互いに素であるため、定理 (オイラーの定理) により、 が成り立ちます。
ゆえに、 を で割ったときの商を 、余りを としたとき、
が成り立ちます。これより、 であることに注意すると、
が成り立つことがわかります。
本問題の制約に合わせて とすると、指数の について、
となりますから、無事指数の値を小さくすることに成功しました。
ここまで、任意の に対して と が互いに素である という仮定の下議論してきましたが、残念ながら今回はそうとは限りません。
しかしながら、以下の補題 によりほぼ同様の結果が得られます。
証明. とし、 の素因数分解が
と与えられているとする。定理 (中国剰余定理) により、
の成立を言うことができれば、 の成立を言える。
そこで、 を任意とする。
が を素因数に持たない場合
が を素因数に持つ場合
さて、 であるから、 である。 仮定と合わせて、 であるから、 により、 が成り立つ。
により、 の成立が言えたので、 が成立する。
ならば、
ならば、
本問題では の場合を考えれば良いので、 の場合は、関数 の定義に従って、愚直に求められるということに注意してください。
と の大小を比較する必要がありますが、以下のいずれかに該当する場合は となり、そうでない場合は が成り立ちます。
かつ
かつ
- かつ
以上をまとめると、関数 の擬似コードは以下のようになります。 なお、
pow(a, b, m)
はpow(a, b)
はtotient(m)
はa[i]
はa % m
は
をそれぞれ表すものとします。
また、任意の非負整数 、正の整数 に対して、
が成り立つこと、さらに、任意の に対して
が成り立つことに注意してください。
function g(i, m) { if (m == 1) return 0; if (i == N) return a[i] % m; if (i == N - 1 && a[i + 1] < 30) { return pow(a[i] % m, a[i + 1], m); } if (i == N - 2) { if ((a[i + 1] <= 5 && a[i + 2] == 2) || (a[i + 1] == 2 && a[i + 2] <= 4) || (a[i + 1] == 3 && a[i + 2] == 3)) { return pow(a[i] % m, pow(a[i + 1], a[i + 2]), m); } } if (i == N - 3) { if (a[i + 1] == 2 && a[i + 2] == 2 && a[i + 3] == 2) { return pow(a[i] % m, 8, m); } } return pow(a[i] % m, g(i + 1, totient(m)) + totient(m), m); }
最後に時間計算量を見積もってみましょう。
まずは、時間計算量を見積もる上で重要となる次の補題を示します。
証明. とし、 の素因数分解が、
と与えられているとする。このとき、定理 により、
が成り立つ。
つ目の形から、 が奇数ならば は偶数となることがわかり、
つ目の形から、 が偶数ならば となることがわかる。
また、任意の に対して、 が成り立つ。
以上により、 が奇数ならば、
が従い、 が偶数ならば、
が従う。よって、 が成り立つ。
先ほどの擬似コードでは、関数 を呼び出すたびに第 引数に を適用しているため、補題 により第 引数の値は急激に小さくなり、やがて となります。よって、高々 回の再帰で停止させることができます。
function g(i, m) { if (m == 1) return 0; if (i == N) return a[i] % m; if (i == N - 1 && a[i + 1] < 30) { return pow(a[i] % m, a[i + 1], m); } if (i == N - 2) { if ((a[i + 1] <= 5 && a[i + 2] == 2) || (a[i + 1] == 2 && a[i + 2] <= 4) || (a[i + 1] == 3 && a[i + 2] == 3)) { return pow(a[i] % m, pow(a[i + 1], a[i + 2]), m); } } if (i == N - 3) { if (a[i + 1] == 2 && a[i + 2] == 2 && a[i + 3] == 2) { return pow(a[i] % m, 8, m); } } return pow(a[i] % m, g(i + 1, totient(m)) + totient(m), m); }
以上を踏まえて、 の計算に かけるとすると、 の計算には全体で、
かかることがわかります。
また、 の計算に かけるとすると、 の計算には全体で、
かかります。
さらに、べき乗 の計算に繰り返し二乗法を用いて かけるとすると、べき乗の計算には全体で、
かかります。
以上から、計算量は全体で となり、本問題の制約において、十分高速に答えを求めることができます。
終わりに
以上、指数タワーの値を任意の mod で求める方法について解説してきましたが、いかがだったでしょうか?
テトレーション *2 の求め方を少し応用するだけで求められる割には、取り組んでいる人がいないように思い、この場を借りて紹介させていただきました。
少しでも面白いと思っていただけたのなら幸いです。
参考文献
[1] yukicoder No.181 A↑↑N mod M - メモ
*1:もし興味があれば、なぜこれが成り立つのか考察してみてください (今回は書ききれなかったので割愛させていただきました…)。