CodeIQ 「ルート・パワー」問題

CodeIQ の @riverplus さん出題の問題。
\Large (1+\sqrt{2}+\sqrt{3}+\sqrt{5})^n
有理数の項を求めよというもの。
他の方のコードが http://togetter.com/li/911507 にある。

\Large \lambda_1 = 1+\sqrt{2}+\sqrt{3}+\sqrt{5}\\ \lambda_2 = 1+\sqrt{2}+\sqrt{3}-\sqrt{5}\\ \lambda_3 = 1+\sqrt{2}-\sqrt{3}+\sqrt{5}\\ \lambda_4 = 1+\sqrt{2}-\sqrt{3}-\sqrt{5}\\ \lambda_5 = 1-\sqrt{2}+\sqrt{3}+\sqrt{5}\\ \lambda_6 = 1-\sqrt{2}+\sqrt{3}-\sqrt{5}\\ \lambda_7 = 1-\sqrt{2}-\sqrt{3}+\sqrt{5}\\ \lambda_8 = 1-\sqrt{2}-\sqrt{3}-\sqrt{5}
とすると、求める数 F(n) は
F(n) = \frac{1}{8} ({\lambda_1}^n + {\lambda_2}^n + {\lambda_3}^n + {\lambda_4}^n + {\lambda_5}^n + {\lambda_6}^n + {\lambda_7}^n + {\lambda_8}^n)
\lambda_1 \sim \lambda_8 を解に持つ方程式
\Large (x-\lambda_1)(x-\lambda_2)(x-\lambda_3)(x-\lambda_4)(x-\lambda_5)(x-\lambda_6)(x-\lambda_7)(x-\lambda_8)=0
を頑張って展開すると
\Large x^8 - 8x^7 - 12x^6 + 184x^5 - 178x^4 - 664x^3 + 580x^2 + 744 x - 71 = 0
になるから、F(n) は差分方程式
\Large F(n+8) - 8F(n+7) - 12F(n+6) + 184F(n+5)\\ - 178F(n+4) - 664F(n+3) + 580F(n+2) + 744F(n+1) - 71F(n) = 0
を満たす。
なので、
\Large v_n = \begin {pmatrix}F(n)\\F(n+1)\\F(n+2)\\F(n+3)\\F(n+4)\\F(n+5)\\F(n+6)\\F(n+7)\end{pmatrix}

\Large A = \begin{pmatrix} & 1 & & & & & & \\ & & 1 & & & & & \\ & & & 1 & & & & \\ & & & & 1 & & & \\ & & & & & 1 & & \\ & & & & & & \;1 & \\ & & & & & & & \;\;1 \\ 71 & -744 & -580 & 664 & 178 & -184 & \;12 & \;\;8 \end{pmatrix}
とすれば
\Large v_{n+1} = A v_n
だから
\Large v_n = A^n v_0
v_0 は直接計算して
\Large v_0 = \begin {pmatrix}1\\1\\11\\31\\285\\1221\\9671\\51171\end{pmatrix}
になる。

下は提出したコードを修正したもの。言語は Ruby

require 'matrix'

def mod_pow(a, n)
  x = Matrix.I(8)
  loop do
    x = (x * a).map {|y| y % Mod} if n.odd?
    return x if (n >>= 1) == 0
    a = (a ** 2).map {|y| y % Mod}
  end
end

def init_mat
  8.times.map do |i|
    if i < 7 then
      8.times.map {|j| j == i + 1 ? 1 : 0}
    else
      [71, -744, -580, 664, 178, -184, 12, 8]
    end
  end
end

Mod = 10 ** 7
a = Matrix[*init_mat]
v = Vector[1, 1, 11, 31, 285, 1221, 9671, 51171]
p (mod_pow(a, gets.to_i) * v)[0] % Mod

Miller-Rabin 素数判定と Pollard のρ法

最近 Project Euler の問題を解いているのだけど、使い回せるコードを探すのが大変になってきたのでここにまとめておくことにする。
前に他のプログラム中で使ったものだが、とりあえず Miller-Rabin 素数判定と Pollard のρ法(またはモンテカルロ法)による因数分解と、このふたつを使った素因数分解のコードを多少修正したもの。言語は Ruby

# Miller-Rabin 法による素数判定
class Miller_Rabin
  class << self
    def mod_pow(b, e, n)
      x = 1
      loop do
        x = x * b % n if e.odd?
        return x if (e >>= 1) == 0
        b = b ** 2 % n
      end
    end

    def init_sd
      @s, @d = 0, @n >> 1
      while @d.even? do
        @s += 1
        @d >>= 1
      end
    end

    def witness?(x)
      x = mod_pow(x, @d, @n)
      return true if x == 1 || x == @n - 1
      @s.times.any? do
        x = x ** 2 % @n
        x == @n - 1
      end
    end

    def probably_prime?(n, k = 20)
      @n = n
      return true if @n == 2 || @n == 3
      return false if @n < 2 || @n.even?
      init_sd
      k.times.all? {witness?(rand(@n - 3) + 2)}
    end
  end
end


# Pollard のρ法による因数分解(Brent のアルゴリズムを使用)
class Rho
  class << self
    M = 64

    def f(x)
      (x ** 2 + @c) % @n
    end

    def init_c
      begin
        @c = rand(@n - 1) + 1
      end while @c == @n - 2
    end

    def brent
      k = 1
      x = y = rand(@n)
      loop do
        k.times {x = f(x)}
        m = [k, M].min
        (k / m).times do
          prod = m.times.inject(1) do |q|
            x = f(x)
            q * (x - y) % @n
          end
          g = @n.gcd(prod)
          return g if g > 1
        end
        y = x
        k <<= 1
      end
    end

    # 非自明な因数をひとつ返す
    def factor(n)
      @n = n
      loop do
        init_c
        q = brent
        return q unless q == @n
      end
    end
  end
end


# 素因数分解
class PF
  class << self
    def factorize(n)
      return if n == 1
      return @d[n] += 1 if Miller_Rabin.probably_prime?(n)
      q = Rho.factor(n)
      factorize(q)
      factorize(n / q)
    end

    # Miller-Rabin 法とρ法により素因数分解を求める
    def prime_division(n)
      @d = Hash.new(0)
      factorize(n)
      @d.sort
    end
  end
end


# 以下使用例

# フェルマー数 F_6
F6 = 2 ** (2 ** 6) + 1

# 素数判定
p Miller_Rabin.probably_prime?(F6)

# 証人数を10として素数判定
p Miller_Rabin.probably_prime?(F6, 10)

# 非自明な因数をひとつ見つける
p Rho.factor(F6)

# 素因数分解
p PF.prime_division(F6)

CodeIQ 「ステップ・アップ・サム」問題

CodeIQ の Kawazoe さん出題の問題。
問題と他の方の解答が http://togetter.com/li/875421 にある。

とりあえず項数 1 の数列を許すとして考える。

項数が奇数のとき、m を項数とすると、(m によって定まる)初項 a(m) は
\Large a(m) = \frac{n}{m} - \frac{m}{2} + \frac{1}{2}
数列が整数列であるための必要十分条件は m が n の約数であること。

項数が偶数のとき、m を 初項+末項 とすると、項数は 2n/m、(m によって定まる)初項 b(m) は
\Large b(m) = \frac{m}{2} - \frac{n}{m} + \frac{1}{2}
数列が整数列であるための必要十分条件は m が n の奇数の約数であること。

初項が正整数となる数列の初項の総和を求めるには、m を n の奇数の約数の範囲で動かし、a(m), b(m) がそれぞれ正のものを選んで足し上げればよい。
m が n の奇数の約数のとき a(m), b(m) は整数、上式より a(m)+b(m)=1 なので、正整数となるのは a(m) と b(m) のうちのどちらか丁度一方で、それは
\Large |\frac{n}{m} - \frac{m}{2}| + \frac{1}{2}
と書ける。
結局、求める値は
\Large \sum_m \left(|\frac{n}{m} - \frac{m}{2}| + \frac{1}{2}\right)\:-\:n
となる。但し m は n の奇数の約数の範囲で動かす。
n を引いているのは項数 1 の数列の寄与を取り除くため。

下が提出したコード。言語は Ruby
n は標準入力から入力する。
素因数分解を利用しているので、n=10^{100} とかならほとんど計算時間はかからない。

require 'prime'

def f(m, k)
  return (m - 2 * @n / m).abs / 2 + 1 unless @factor[k]
  (0..@factor[k][1]).map {|e| f(m * @factor[k][0] ** e, k + 1)}.inject(:+)
end

@n = gets.to_i
@factor = Prime.prime_division(@n)
p f(1, @n % 2 == 0 ? 1 : 0) - @n

CodeIQ 「スロット・マシン」問題

CodeIQ の Kawazoe さん出題の問題。
問題と他の方のコードが http://togetter.com/li/866620 にある。

\large a_0, a_1,\ldots, a_9 をそれぞれ 0〜9 の数字の出現回数とする。
\large a_0,\ldots, a_9 の10個の数を昇順に並べ替えたものを \large b_1, b_2,\ldots, b_{10} とする。
例えば n=4 のとき \large b_k\;(k=1,2,\ldots,10) の組は次のいずれかになる。

0, 0, 0, 0, 0, 0, 1, 1, 1, 1
0, 0, 0, 0, 0, 0, 0, 1, 1, 2
0, 0, 0, 0, 0, 0, 0, 0, 2, 2
0, 0, 0, 0, 0, 0, 0, 0, 1, 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 4

b_k の中で同じ数が並んでいる部分列をグループと呼ぶことにする。
グループ g で並んでいる数を b(g)、列の長さを r(g) と書くことにする。
例えば上の最初の例では g_1 = \{0,0,0,0,0,0\}g_2=\{1,1,1,1\} のふたつのグループが存在し、b(g_1) = 0,\;r(g_1) = 6,\;b(g_2) = 1,\;r(g_2) = 4
b_k が与えられたとき、それに対応する組み合わせは
\Large n! 10! \: \{\prod_g r(g)! \, b(g)!^{r(g)}\}^{-1}
で計算でき({\rm \Pi}_gb_k に含まれるグループについての積)、b_k に対応する数字の並びが出現する確率はこれを 10^n で割ったものになる。
この式は解法pdfの計算法の書き方を変えただけのものなので、証明は省略。

プログラムではメモ化再帰により b_k を、k=10 から始めて k の小さいほうへ向かって生成している(b_k 自体をあらわには扱っていないけど)。再帰1段がグループ1個ぶんの処理。

グループの要素数が取り得る最大値を取る場合、b_k の組をまとめて計算できる。
例えば n=11 で b_{10}=4,\: b_9=4 のとき、値が 4 のグループは要素数が最大値 2。
このとき可能な b_k

0, 0, 0, 0, 0, 1, 1, 1, 4, 4
0, 0, 0, 0, 0, 0, 1, 2, 4, 4
0, 0, 0, 0, 0, 0, 0, 3, 4, 4

の3通りあるが、これらのいずれかが出現する組み合わせは
\Large \frac{11! \cdot 10!}{4!^2 \cdot 2!} \, \frac{8^3}{3! 8!}
とまとめて計算できる。
(例えば 0 と 1 が4個ずつ出現すると決めた時点で、残り3個のリールには 2〜9 の8種の数字がどう出現してもいいとかそういうこと)。こうすることで、全ての b_k の列を列挙するコードと較べて、計算速度が約2倍になる。

下が提出したコードを少し修正したもの。
http://ideone.com/WNELWN に ideone.com での実行結果がある。F(100) だけの計算だと0.16秒だった。

# 着目しているグループの最も大きなインデックスが k
# b_k = b, b1 + b2 + … + b_k = m
def calc(b, m, k)
  @memo[b | m << @shift | k << 2 * @shift] ||= begin
    # r はグループの要素数。まず取り得る最小値とする
    r = [m - k * (b - 1), 1].max
    m -= b * r
    k -= r
    denom = @fac[r] * @fac[b] ** r
    sum = 0
    # このループ1周ごとにグループの要素数 r を1ずつふやしていく
    while m >= b do
      sum += (-(-m / k)...b).inject(0) {|s, c| s + calc(c, m, k)} / denom
      m -= b
      k -= 1
      denom *= (r += 1) * @fac[b]
    end
    # グループの要素数が最大値を取る場合
    # 上で説明したように簡単に計算できる
    sum + @num / (denom * @fac[k] * @fac[m]) * k ** m
  end
end

# F(n) を文字列で返す
def f(n)
  # 階乗  @fac[k] = k!
  @fac = (1..[n, 10].max).each_with_object([1]) {|k, fc| fc << k * fc.last}
  @num = @fac[n] * @fac[10]
  @memo = {}
  @shift = n.to_s(2).size
  (-(-n / 10)..n).inject(0) {|s, b| s + b * calc(b, n, 10)}.
  to_s.insert(-n - 1, '.').sub(/\.?0*$/, '')
end

puts f(6)
puts f(12)
puts f(100)

CodeIQ 「カット・アンド・スクエア」問題

CodeIQ の Kawazoe さん出題の問題。
問題と他の方のコードが CodeIQ「カット・アンド・スクエア」問題 みんなのコード にまとまっている。

\Large m = 10^{\frac{n}{2}}
とする。
求める n 桁の整数の上下 n/2 桁をそれぞれ x, y とすると
\Large x^2 + y^2 = mx + y
が成り立つ。
変形して
\Large (2x-m)^2 + (2y-1)^2 = m^2 + 1
\Large u=2x-m,\; v=2y-1
として
\Large u^2 + v^2 = m^2 + 1

つまり m^2+1 を2平方数の和で表す方法を列挙できればよい。
u^2 + v^2ガウス整数の積に分解して
\Large (u + {\rm i}v) (u - {\rm i}v) = m^2 + 1
だから、m^2+1ガウス素数への分解が与えられれば、u, v の組は簡単に列挙できる。
このためには、まず m^2+1 を有理素数素因数分解して、各有理素数ガウス素数に分解すればよい。

例えば n=4 のとき、上の式は
\Large (u+{\rm i}v)(u-{\rm i}v) = 10001
で、10001 = 73\cdot 137素因数分解できる。73, 137 をそれぞれ 73 = (8+3{\rm i})(8-3{\rm i}),\; 137 = (11+ 4{\rm i})(11-4{\rm i})ガウス素数に分解して、
\Large (u+{\rm i}v)(u-{\rm i}v) = (8+3{\rm i})(8-3{\rm i})(11+ 4{\rm i})(11-4{\rm i})
右辺のガウス素数を左辺の因数に適当に割り振る。例えば
\Large u+{\rm i}v = (8+3{\rm i})(11+ 4{\rm i}),\; u-{\rm i}v = (8-3{\rm i})(11-4{\rm i})
とすれば u = 76,\; v = 65 となって、76^2+65^2 = 10001 という解が得られる。
同様に
\Large u+{\rm i}v = (8+3{\rm i})(11- 4{\rm i}),\; u-{\rm i}v = (8-3{\rm i})(11+4{\rm i})
とすれば u = 100,\; v = 1 となって、100^2+1^2 = 10001 を得る(こちらは自明だけど)。

いちばん下にあるコードが提出したコードを少し修正したもの。言語は Ruby
n=36 まで計算して ideone.com での実行時間は今走らせたら 0.65秒だった → http://ideone.com/mGfU6N (乱数を使っているので実行時間はばらつきがある)。
提出したものは浮動小数点の平方根 Math.sqrt を使っていたのだが、それだと丸め誤差のせいで大きな n でおかしくなるので、整数の平方根を自前で計算するように修正した。
有理素数への分解は Miller-Rabin 法と Pollard のρ法 (Pollard's rho algorithm)、有理素数ガウス素数への分解は Cornacchia-Smith のアルゴリズムによった。

m^2+1 の有理素数の素因数 q の片方のガウス素数の素因数は q と m+{\rm i} の共通の因数になる。
Cornacchia-Smith のアルゴリズムのところは(ガウス整数に対して gcd というのはたぶん書き方変だが) {\rm gcd}(q, m+{\rm i})ユークリッドの互除方で求めるようなことをしている。
有理素数が4k+3型だと分解できないが、m^2+1 の素因数は平方剰余の第1補充法則により4k+3型にはならないので、分解できない場合の処理は省略できる。
ユークリッドの互除法を呼んでいるところ

    u = euclid(q, m, sqrt(q))

がちょっと怪しげで、英語版 Wikipedia の記事によるとここは

    r = m % q
    r = q - r if 2 * r > q
    u = euclid(r, q, sqrt(q))

とでもなるところだけど*1、こうでなくてもテストした範囲では大丈夫そうなのでそのままにしてある。
実はここのところを書いたのがかなり前で、そのときは分かっていてこういう書き方になったのかもしれない。

(追記)
やっぱり r=q-r なんて要らないと思う。だって 2r>q なら互除法の最初の1段で r=q-r と同じ計算が行われるではないか。ということで r=m%q も要らなくて r のところにはそのまま m を突っ込んでもいいはず。

# Miller-Rabin法による素数判定
class Miller_Rabin
  def init_sd
    @s, @d = 0, @n >> 1
    while @d.even? do
      @s += 1
      @d >>= 1
    end
  end

  def witness?(x)
    x = mod_pow(x, @d, @n)
    return true if x == 1 || x == @n - 1
    @s.times.any? do
      x = x ** 2 % @n
      x == @n - 1
    end
  end

  def probably_prime?(n, k = 20)
    @n = n
    return true if @n == 2
    return false if @n < 2 || @n.even?
    init_sd
    k.times.all? {witness?(rand(@n - 1) + 1)}
  end
end

# ρ法による因数分解(Brent のアルゴリズムを使用)
class Rho
  M = 64

  def f(x)
    (x ** 2 + @c) % @n
  end

  def init_c
    begin
      @c = rand(@n - 1) + 1
    end while @c == @n - 2
  end

  def factor(n)
    @n = n
    init_c
    k = 1
    x = y = rand(@n)
    loop do
      k.times {x = f(x)}
      m = [k, M].min
      (k / m).times do
        prod = m.times.inject(1) do |q|
          x = f(x)
          q * (x - y) % @n
        end
        g = @n.gcd(prod)
        return g if g > 1
      end
      y = x
      k <<= 1
    end
  end
end

# 素因数分解
class PF
  def factorize(n)
    return if n == 1
    return @d[n] += 1 if Miller_Rabin.new.probably_prime?(n)
    q = Rho.new.factor(n)
    factorize(q)
    factorize(n / q)
  end

  # Miller-Rabin法とρ法により素因数分解を求める
  def prime_division(n)
    @d = Hash.new(0)
    factorize(n)
    @d.sort
  end
end

# b ** e % n を計算
def mod_pow(b, e, n)
  x = 1
  loop do
    x = x * b % n if e.odd?
    return x if (e >>= 1) == 0
    b = b ** 2 % n
  end
end

# ここまで(有理素数への)素因数分解のためのコード
# (ライブラリの素因数分解が速ければ必要ない)

require 'complex'

# 正整数 a に対して floor(sqrt(a)) を求める
def sqrt(a)
  x, y = 1 << a.to_s(4).size, a + 2
  x, y = (x + a / x) / 2, x while x < y
  y
end

# ユークリッドの互除法
def euclid(x, y, c)
  x <= c ? x : euclid(y % x, x, c)
end

# m^2 + 1 をガウス素数の積に分解
def calc_gdiv(m)
  PF.new.prime_division(m ** 2 + 1).flat_map do |q, e|
    # Cornacchia-Smith のアルゴリズムにより有理素数 q をガウス素数に分解
    # m^2+1 が 4k+1 型の素因数しか持たないことと
    # 各素因数 q について m が -1 の mod q での平方根であることを利用して
    # もとのアルゴリズムを簡略化してある
    u = euclid(q, m, sqrt(q))
    v = sqrt(q - u ** 2)
    [u + v * Complex::I] * e
  end
end

# u^2 + v^2 = m^2 + 1 となる非負整数の組 (u, v) を列挙
def div2squares(gdiv, z = gdiv[0], k = 1)
  return [[z.real.abs, z.imag.abs]] if k == gdiv.size
  div2squares(gdiv, z * gdiv[k], k + 1) + div2squares(gdiv, z * gdiv[k].conj, k + 1)
end

# 問題の条件を満たす整数を列挙
def solve(n)
  m = 10 ** (n / 2)
  div2squares(calc_gdiv(m)).flat_map {|u, v|
    u, v = v, u if u.odd?
    [[u, v], [-u, v]]
  }.uniq.map {|u, v| [(u + m) / 2, (v + 1) / 2]}.
  reject {|x, y| x < m / 10 || y <= 1}.
  map {|x, y| m * x + y}.sort
end

# n=36 まで、問題の条件を満たす整数、それらの整数の個数と和を出力
2.step(36, 2) do |n|
  puts "n = #{n}"
  puts a = solve(n)
  puts "n_solutions = #{a.size}\nsum = #{a.inject(0, :+)}\n\n"
end

*1:別のところでの説明ではここの不等号の向きが逆になっていて余計に話がややこしい