CodeIQ, ディビジョン・サム 復習

CodeIQ の解説のコードを勉強がてらに自分なりに書き直してみる。元のコードはリストを使って、他の言語にもある程度似せて書ける様になっている。自分が書いたコードは、純粋に Python のみを対象にしたもの。

"""Devision Sum.

数学の問題をプログラミングで解こう!「ディビジョン・サム」問題解説
https://codeiq.jp/magazine/2016/12/47476/

###mod 式
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1349018013


###Data Structure.
An dict's key describes prime and dict's value is exponent.

    prime_factors[prime] = exponent
"""
import math


def calc_devision_sum(n, D):
    # 1)
    prime_factors_of_factorial\
        = calc_prime_factors_of_factorial(n)

    # 2)
    prime_factors_of_factorial_to_the_nth_power\
        = calc_prime_factors_of_factorial_to_the_nth_power(prime_factors_of_factorial)

    # 3)
    aliquot_sum_mod_of_factorial_to_the_nth_power\
        = calc_aliquot_sum_mod(prime_factors_of_factorial_to_the_nth_power, D)

    return aliquot_sum_mod_of_factorial_to_the_nth_power


#
# 1)
#
def calc_prime_factors_of_factorial(n):
    """Create an prime factors of factorial."""
    # http://examist.jp/mathematics/integer/kaijyou-soinsu/
    prime_list = sieve_of_eratosthenes(n)
    prime_factors_of_factorial = dict(zip(prime_list, [0] * len(prime_list)))
    for prime in prime_list:
        k = prime
        while k <= n:
            prime_factors_of_factorial[prime] += n // k
            k *= prime
    return prime_factors_of_factorial


def sieve_of_eratosthenes(n):
    """Get prime list less than or equal to n.

    1) エラトステネスのふるいとその計算量
    http://mathtrain.jp/eratosthenes
    "なお,素数定理とかを使えば
    愚直な方法に対して
    もう少し計算量を厳しく評価できますが,
    とりあえずエラトステネスのふるいの方が
    速いことを理解しておいてください。"

    ### Data Structure
    is_prime[index]
      list of boolean,
      an each element describes its index number is prime or not.
    ex)
    is_prime[1] = False
    is_prime[4] = False
    is_prime[7] = True
    """
    is_prime = [True] * (n + 1)
    is_prime[0] = False
    is_prime[1] = False
    # faster than the below code
    # for i in range(2, n) + 1):
    for i in range(2, int(math.sqrt(n)) + 1):
        for j in range(2 * i, n + 1, i):
            is_prime[j] = False
    prime_list = [i for i in range(n + 1) if is_prime[i]]
    return prime_list


#
# 2)
#
def calc_prime_factors_of_factorial_to_the_nth_power(prime_factors_of_factorial):
    return {base: exponent * n for base, exponent in prime_factors_of_factorial.items()}


#
# 3)
#
def calc_aliquot_sum_mod(prime_factors, D):
    """約数の和は等比数列の和で表現できる."""
    aliquot_sum_mod = 1
    for base, exponent in prime_factors.items():
        # 等比数列の和の剰余
        t = ((pow_mod(base, exponent + 1, D) - 1) * inv_mod(base - 1, D)) % D
        # 約数の和の剰余
        aliquot_sum_mod = (aliquot_sum_mod * t) % D
    return aliquot_sum_mod


def pow_mod(a, n, d):
    """Modular exponentiation, a^n mod d.

    "結局、分配則が成り立つので、途中で剰余を撮ってしまえば良い。"
    https://ja.wikipedia.org/wiki/%E5%86%AA%E5%89%B0%E4%BD%99
    """
    # 実は組み込み関数がある。
    # return pow(a, n, d)
    if n == 0:
        return 1
    elif n % 2 == 0:
        return pow_mod((a * a) % d, n // 2, d)
    else:
        return a * pow_mod(a, n - 1, d) % d


def inv_mod(a, d):
    """単位元と逆元.

    # d を法とする a の逆元 (a*x = 1 (mod d) となる x) を求める.
    http://www.geocities.jp/k27c8_math/math/group/identity_inverse.htm
    """
    (p, q) = gcd_ext(a, d)
    return p % d


def gcd_ext(a, b):
    """拡張ユークリッド互除法.

    # 拡張ユークリッドの互除法
    # a*x + b*y = gcd(a,b) となる (x,y) を求める.
    http://www.tbasic.org/reference/old/ExEuclid.html
     逆元の求め方も書いてある。
    """
    x, y, lastx, lasty = 0, 1, 1, 0
    while b != 0:
        q = a // b
        a, b = b, a % b
        x, y, lastx, lasty = lastx - q * x, lasty - q * y, x, y
    return (lastx, lasty)


#
# main
#
if __name__ == '__main__':
    n = int(input())
    D = 1000003
    print(calc_devision_sum(n, D))