D - Factorial and Multiple Editorial by miscalculation53
ルジャンドルの定理と二分探索を用いた別解次の判定問題を考えます。
整数 \(N, K\) が与えられる。\(N!\) は \(K\) の倍数か?
これが解ければ、\(N\) の値を二分探索することで答えを求めることができます。以下、この判定問題の解法を考えます。
ここで、ルジャンドルの定理 とよばれる次の定理が使えます。
\(n!\) が素数 \(p\) で割り切れる回数は、\(\displaystyle \sum_{i = 1}^{\infty} \left\lfloor \frac{n}{p^i} \right\rfloor = \left\lfloor \frac{n}{p} \right\rfloor + \left\lfloor \frac{n}{p^2} \right\rfloor + \left\lfloor \frac{n}{p^3} \right\rfloor + \cdots\) である。
ルジャンドルの定理がよくわからない人向けの説明(証明ではない)
高校数学で、
$100!$ は $5$ で何回割り切れるか?という感じの問題を見たことがある人もいると思います。これは、次のように考えればよいです。
- $100! = \underbrace{1 \times 2 \times \cdots \times 100}_{100 \ 個}$ のうちで、$5$ で割り切れる回数にかかわるのは $\underbrace{5 \times 10 \times \cdots \times 100}_{20 \ 個}$ のところである。これはとりあえず $5$ で $20$ 回割れて、割ると $\underbrace{1 \times 2 \times \cdots \times 20}_{20 \ 個}$ になる。
- そのうちで、$5$ で割れる部分がまだ残っている。$5 \times 10 \times 15 \times 20$ のところである。これは $5$ で $4$ 回割れて $1 \times 2 \times 3 \times 4$ になる。
- もう $5$ で割れる部分は残っていない。全部で $20 + 4 = 24$ 回割り切れた。
ルジャンドルの定理は、一般の場合についてこれと同じようなことをやっています。
この定理を用いれば、判定問題は
- \(K\) を素因数分解する。
- \(K\) のすべての素因数 \(p\) について、\((N! \ を \ p \ で割り切れる回数) \geq (K \ を \ p \ で割り切れる回数)\) が成り立っているか調べる。
というアルゴリズムで解けます。このアルゴリズムの計算量は、素因数分解がボトルネックで \(O(\sqrt{K})\) です。素因数分解は \(1\) 回前計算すればよいので、全体でも \(O(\sqrt{K})\) で解くことができました。
Python による実装例:
k = int(input())
# 素因数分解
pes = list() # 素因数 p と 指数 e の組を格納
k2 = k
p = 2
while p * p <= k:
e = 0
while k2 % p == 0:
k2 //= p
e += 1
if e > 0:
pes.append((p, e))
p += 1
if k2 > 1:
pes.append((k2, 1))
# n! は p で何回割り切れるか(ルジャンドルの定理)
def legendre(n, p):
res = 0
p2 = p
while True:
tmp = n // p2
if tmp == 0:
break
res += tmp
p2 *= p
return res
# 判定問題
def isok(n):
for p, e in pes:
if legendre(n, p) < e:
return False
return True
# 二分探索
ng, ok = 1, k # k ≥ 2 なので 1! は k の倍数にならない、また k! は必ず k の倍数
while abs(ok - ng) > 1:
mid = (ok + ng) // 2
if isok(mid):
ok = mid
else:
ng = mid
print(ok)
posted:
last update: