🐢

[鉄則A29] Power (繰り返し二乗法)

2024/05/19に公開

https://atcoder.jp/contests/tessoku-book/tasks/math_and_algorithm_aq

pow の内部実装(繰り返し二乗法)が学べる問題。

問題を要約する

  • a の b 乗を 1000000007 で割った余りで出力せよ

考え方

余りで丸めるのはいったん置いといて3の10乗で考える。答えは、

3**10  # => 59049

となるのだが、3 を 10 回掛けてはいけない。

3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3 * 3  # => 59049

正しくは二進数の桁の値を指数として基数 3 を累乗する。

3**1  # => 3
3**2  # => 9
3**4  # => 81
3**8  # => 6561
8乗 4乗 2乗 1乗
6561 81 9 3

次に指数は 10 なので対応する 8 乗と 2 乗の位置の値を掛ける。

6561 * 9  # => 59049

これだけ。あとはこの計算の途中で毎回余りを求めるようにする。

単純な実装 (TLE)

a, b = gets.split.collect(&:to_i)  # => [3, 10]
MOD = 1000000007
acc = 1
b.times { acc = (acc * a).modulo(MOD) }
p acc                              # => 59049

a を b 回掛ける方法では TLE になる。指数ことループ回数が多くなると破綻する。

解説の模範解答 (AC)

135 ms
a, b = gets.split.collect(&:to_i)  # => [97, 998244353]
MOD = 1000000007
acc = 1
x = a
30.times do |i|
  if b.anybits?(1 << i)
    acc = (acc * x).modulo(MOD)    # => 97, 477410631, 400447012, 832709201, 594079130, 593767246, 934801994
  end
  x = (x * x).modulo(MOD)          # => 9409, 88529281, 539514930, 655370401, 500328288, 20712186, 645895638, 267158840, 290528685, 216980813, 880576265, 53448354, 525312234, 258400169, 871834039, 238391419, 255020285, 306233810, 730662085, 719477679, 955690159, 616039570, 149251657, 961313257, 615085852, 682251690, 249584545, 666809917, 297098655, 184935755
end
p acc                              # => 934801994

急に出てきたマジックナンバー 30 とは何だろうか? これは b の最大が 10**9 で、それを超えない最大が 2**29 のため回数で言えば 30 回ということらしい。

10**9             # => 1000000000
(2**29)..(2**30)  # => 536870912..1073741824

リファクタリング後 (AC)

44 ms
a, b = gets.split.collect(&:to_i)  # => [97, 998244353]
MOD = 1000000007
acc = 1
x = a
until b.zero?
  if b.anybits?(1)
    acc = (acc * x).modulo(MOD)    # => 97, 477410631, 400447012, 832709201, 594079130, 593767246, 934801994
  end
  b = b >> 1
  x = (x * x).modulo(MOD)          # => 9409, 88529281, 539514930, 655370401, 500328288, 20712186, 645895638, 267158840, 290528685, 216980813, 880576265, 53448354, 525312234, 258400169, 871834039, 238391419, 255020285, 306233810, 730662085, 719477679, 955690159, 616039570, 149251657, 961313257, 615085852, 682251690, 249584545, 666809917, 297098655, 184935755
end
p acc                              # => 934801994

マジックナンバー 30 を消したもの。b を右シフトして 0 になったら早めに終わるようにすればすっきりして計算時間も短かくなる。

Ruby の Integer#pow 実装

https://github.com/ruby/ruby/blob/2f915e729ac8c66f4009f4b28a57773923d7e7d1/bignum.c#L6988-L7016

似たようなコードになっているのがわかる。

a.pow(b, MOD) を使う

Ruby の実装を見ていて気づいたが pow には第二引数があって、

45 ms
a, b = gets.split.collect(&:to_i)  # => [97, 998244353]
MOD = 1000000007
p a.pow(b, MOD)                    # => 934801994

** や pow をそのまま使うと、

a**b      # => Infinity
a.pow(b)  # => Infinity

となってしまうが第二引数に modulo の引数を渡せば、

a.pow(b, MOD)  # => 934801994

内部で丸めてくれる。

参考

Discussion