繰り返し二乗法
計算量
をで求めることができます。
普通に計算しようとするとを回かけるので、かかります。
仕組み
例としてを解いてみましょう。
理由はこれから述べますが繰り返し二乗法ではまず、指数を二進数に変換します。
今回だとなので
となりますね。
これをさらに
と変換します。
これを指数に使うと
このようにして2の累乗の形に変換できました。
そして は、前の値を二乗するだけで求まります。
つまり、二進数化した後にその桁数分ループを回し、ループごとににをかけていく。1が立っていれば答えに今のをかけてあげることで計算量を落としつつ結果を得られるということです。
文字だけ見てもあれなので、この後に載せるコード例を見ると想像つきやすいと思います。
一般に、を2進数で表現したときその桁数は であり、これがそのままループ数と対応するため回の掛け算を回に抑えることができます。
ll power(ll a, ll n) {
ll result = 1;
while (n > 0) {
// nの最下位ビットが1ならresultに掛ける
if (n & 1) result *= a;
a *= a;
// n を右シフトして次のビットへ
n >>= 1;
}
return result;
}
実際にはが大きな数になってしまうためを取るような問題が多いです。
掛けるたびにを取ってあげればいいですね(これは頻出テクニックです。知らなかった人は先にのテクニックについて調べてください)。
例題
以下の例題を解いてみましょう。
問題文
を1000000007で割った余りを求めてください。
制約
が最大なので、普通にで計算しようとするとTLEします。繰り返し二乗法でに落としましょう。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1000000007;
ll power(ll a, ll n, ll mod) {
ll result = 1;
a %= mod;
while (n > 0) {
if (n & 1) result = result * a % mod;
a = a * a % mod;
n >>= 1;
}
return result;
}
int main() {
ll a, b;
cin >> a >> b;
cout << power(a, b, MOD) << endl;
return 0;
}
掛け算をするたびに% modを取っています。result * aの前にresultは既にmod未満なので、オーバーフローを防ぐためにはaもmod未満であることが必要です。そのため最初にa %= modとしています。
その他の例題
まとめ
指数が大きいときの累乗計算は繰り返し二乗法が基本です。
難しい内容でもなくこのアルゴリズムだけで解けるような問題だとAtCoderではB、C辺りで頻出なので、オーバーフローを起こして爆死することのないよう知っておくといいでしょう。
もしくはpythonを使いましょう(メモリが許す限り桁が無限なので)。