みたいなのを高速に計算しようと思ったときに、繰り返し二乗法というテクニックがあります。 今回は、繰り返し二乗法の中身について頭の整理のメモです。
繰り返し二乗法
繰り返し二乗法は、 みたいな値を高速に計算するテクニックです。
アイデアとしては、
のように、指数が2のべき乗になっている時、指数/2したものを二乗することで計算します。 こうすることで、単純に指数回だけ2を掛けるより高速に結果を求めることができます。
をGoの関数で書くと、こんな感じになります。
func pow(a int, n int) int { if n == 1 { return a } if n%2 == 1 { return a * pow(a, n-1) } ret := pow(a, n/2) ret = (ret * ret) return ret }
書いてみると意外とシンプルですが、これが結構高速化に役立ちます。
例題
解説を見るとフェルマーの小定理を使った式変形とか、1000000007の余りをとったりと、若干(?)他にもポイントはありますが、その辺すっ飛ばしてやってみます。
package main import ( "fmt" ) func main() { var n, a, b int fmt.Scanf("%d %d %d", &n, &a, &b) mod := int(1e9) + 7 ans := (powMod(2, n, mod) - 1 - nCrMod(n, a, mod) - nCrMod(n, b, mod)) % mod if ans < 0 { ans += mod } fmt.Println(ans) } func powMod(a int, n int, b int) int { if n == 1 { return a } if n%2 == 1 { return a * powMod(a, n-1, b) % b } ret := powMod(a, n/2, b) ret = (ret * ret) % b return ret } func nCrMod(n int, r int, mod int) int { ret := 1 //X for i := n - r + 1; i <= n; i++ { ret *= i ret %= mod } //Y for i := 2; i <= r; i++ { ret *= powMod(i, mod-2, mod) ret %= mod } ret %= mod if ret < 0 { ret += mod } return ret }
単純にべき乗を取るわけではなく、1e9+7で割った余りを計算するように変形されていますが、基本的なアイデアは変わっていません。式変形ができてしまえば、繰り返し二乗法を使うことで高速に計算出来る例でした。
感想
勉強になりました。