問題
2つの整数 $L, R$($L \le R$)が与えられる。区間 $[L, R]$ に含まれる素数の個数を求めよ。
制約
| パラメータ | 範囲 | 備考 |
|---|---|---|
| $L, R$ | $1 \le L \le R \le 10^{12}$ | 大きな上限 |
| $R-L$ | $R-L \le 10^6$ | 区間幅は小さい |
入出力例
入力例1
10 20
出力例1
4
区間$[10,20]$の素数は$11,13,17,19$の4個。
入力例2
999999999989 1000000000000
出力例2
1
概念図
ヒント
ヒント1(方向性)
$R\le10^{12}$に対して通常の篩を$0$〜$R$まで直接作るとメモリも時間も足りない。合成数は必ず$\sqrt{R}$以下の素因数を持つという性質を使う。
ヒント2(アプローチ)
まず$\sqrt{R}$までの素数を通常の篩で求める。次に区間$[L,R]$に対応する配列(オフセットL)を用意し、各素数$p$について区間内の倍数を篩い落とす。
ヒント3(ほぼ答え)
limit = int(math.isqrt(R)) + 1
# limitまでの通常の篩でbase_primesを得る
size = R - L + 1
is_prime_range = [True] * size
for p in base_primes:
start = max(p * p, ((L + p - 1) // p) * p)
for multiple in range(start, R + 1, p):
is_prime_range[multiple - L] = False
模範解答
import sys
import math
def main():
data = sys.stdin.buffer.read().split()
L = int(data[0])
R = int(data[1])
if R < 2:
print(0)
return
limit = int(math.isqrt(R)) + 1
is_prime_small = [True] * (limit + 1)
is_prime_small[0] = False
if limit >= 1:
is_prime_small[1] = False
for i in range(2, int(math.isqrt(limit)) + 1):
if is_prime_small[i]:
for j in range(i * i, limit + 1, i):
is_prime_small[j] = False
base_primes = [i for i, v in enumerate(is_prime_small) if v]
size = R - L + 1
is_prime_range = [True] * size
if L == 0:
if size > 0:
is_prime_range[0] = False
if size > 1:
is_prime_range[1] = False
elif L == 1:
is_prime_range[0] = False
for p in base_primes:
start = max(p * p, ((L + p - 1) // p) * p)
for multiple in range(start, R + 1, p):
is_prime_range[multiple - L] = False
print(sum(is_prime_range))
main()
計算量: $\sqrt{R}$までの篩が$O(\sqrt{R}\log\log\sqrt{R})$、区間篩が$O((R-L)\log\log R)$程度。
Step-by-Step 解説
Step 1: base_primes($\sqrt{R}$以下の素数)を求める
合成数$x\le R$は必ず$\sqrt{x}\le\sqrt{R}$以下の素因数を持つため、その範囲の素数さえ分かれば区間内の合成数を全て篩い落とせる。
Step 2: 区間配列の準備とオフセット管理
is_prime_range[i]が実数L+iに対応するように配列を作る。
Step 3: 各素数$p$について区間内の倍数を篩い落とす
start = max(p*p, ceil(L/p)*p)として不要な走査を避ける。
Step 4: $L\le1$の特別処理と集計
1は素数ではないため区間に含まれる場合は明示的に除外し、最後にTrueの個数を数える。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| $R$まで直接篩を作りMLE/TLE | $R\le10^{12}$を見落とす | $\sqrt{R}$までの篩だけを作る |
| $L=1$で1を素数として数える | 素数の定義を見落とす | 区間篩配列で明示的に除外 |
| オフセット変換を忘れ配列外アクセス | 実数値をそのまま添字に使う | 添字は常に「実数値 − L」で計算 |
startを$L$からにして計算量悪化 | $p^2$未満はすでに篩われている事実を使わない | start=max(p*p, ...)で無駄な走査を避ける |
次のステップ
- 発展: 区間内の素因数分解(各数の最小素因数を区間篩で同時記録)
- 発展: Lucy_Hedgehogの篩(Day078 Q5)との比較
- 次回予告: 未定(Master Levelローテーション継続)
自己評価
理解度: / /
自分の回答:
気づき・メモ: