2-6 数学的な問題を解くコツ
ユークリッドの互除法
最大公約数を求める
# 線分上の格子点の個数
def gcd(a: int, b: int) -> int:
if b == 0:
return a
return gcd(b, a % b)
自然数 に対して、最大公約数を求める関数を とする。
を で割った商と余りを とする。このとき、
とすると、自然数 を利用して、
と表せ、これらを に代入すると、
により、 は を割り切る。同様にして、 は を割り切る。よって、
が示された。
拡張ユークリッドの互除法
# 双六
def extgcd(a: int, b: int, x: int, y: int) -> int:
d = a
if b != 0:
d = extgcd(b, a % b, y, x)
else:
x = 1
y = 0
return d
題意は となる整数 を求めることと同値である。
のとき解は存在しないため、以下 を仮定する。
の解が求まっているとする。
このとき、
であるからこれを代入し、 に関して整理すると、
つまり、 を再帰的に計算することで解が求まる。
素数に関する基本的なアルゴリズム
素数判定
# 素数判定
def is_prime(n: int) -> bool:
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
# 約数の列挙
def divisor(n: int) -> list[int]:
res: list[int] = []
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
res.append(i)
if i != n // i:
res.append(n // i)
return res
# 素因数分解
def prime_factor(n: int) -> dict[int, int]:
res: dict[int, int] = {}
for i in range(2, int(n**0.5) + 1):
while n % i == 0:
res[i] = res.get(i, 0) + 1
n //= i
if n != 1:
res[n] = 1
return res
💡
関連: フェルマーテスト、Carmichael Numbers(カーマイケル数)
Tips: 約数の個数はそう多くない
を の約数の個数とすると、
- 以下の場合: で
- 以下の場合: で
- 以下の場合: で
参考: https://twitter.com/e869120/status/1412541885160189952/photo/2 (opens in a new tab)
エラトステネスの篩
# 素数の個数
MAX_N: int = 20
prime: list[int] = [0] * MAX_N
is_prime: list[int] = [True] * (MAX_N + 1)
def sieve(n: int) -> int:
p: int = 0
is_prime[:2] = [False] * 2
for i in range(2, n + 1):
if is_prime[i]:
prime[(p := p + 1)] = i
for j in range(2 * i, n + 1, i):
is_prime[j] = False
return p
区間篩
未満の素数でない整数の最小の素因数は高々 であることを利用して、 と の 2 つの篩を同時に更新していく。
# 区間内の素数の個数
MAX_L: int
MAX_SQRT_B: int
is_prime: list[bool] = [True] * MAX_L
is_prime_small: list[bool] = [True] * MAX_SQRT_B
def segment_sieve(a: int, b: int) -> None:
for i in range(2, int(b**0.5)):
if is_prime_small[i]:
for j in range(2 * i, int(b**0.5), i):
is_prime_small[j] = False
for j in range(max(2, a + i - 1 // i), b, i):
is_prime[j - a] = False
余りの計算
合同式
モジュラ逆数
は、 であるから、 を計算するには、P * pow(Q, -1, mod) % mod
練習問題
べき乗を高速に計算する
繰り返し二乗法
- と 任意の数が 2 の冪乗和で表現出来ることを利用し、累乗を高速化するテクニック
- 例:
- 計算量は
# Carmichael Numbers
def mod_pow(x: int, n: int, mod: int) -> int:
res: int = 1
while n > 0:
if n & 1:
res = res * x % mod
x = x * x % mod
n >>= 1
return res
# 再帰ver
def mod_pow(x: int, n: int, mod: int) -> int:
if n == 0:
return 1
return mod_pow(x * x % mod, n // 2, mod) * x**(n & 1) % mod
付録
エラトステネスの篩
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |