AtCoder NoviStepsを埋めてみる(7) 再帰全探索 1Q以降の続きです。今回はエラトステネスの篩です。

エラトステネスの篩とは?

エラトステネスの篩は、N 以下の正の素数を O(N loglog N) の計算量で列挙するアルゴリズムです。古代ギリシアの科学者、エラトステネスが考案したといわれています。

1 ~ n までの数字を書く
1 は素数ではないので消す
残った数のなかで最小の 2 は素数。それ以外の 2 の倍数は素数ではないので消す。
残った数のなかで次に最小の 3 は素数。それ以外の 3 の倍数は素数ではないので消す。
残った数のなかで次に最小の 5 は素数。それ以外の 5 の倍数は素数ではないので消す。
これを繰り返すと最終的に素数のみが残る。

篩い落とし操作は n の平方根 までやれば充分です。これだと n の平方根 より大きな素数による合成数を消すことができませんが、これは n よりも大きな値となるので気にする必要はありません。

あとこんなメソッドも定義しておきます。

B26 – Output Prime Numbers

B26 – Output Prime Numbers

問題の概要

N 以下の素数を値の小さい方から全て出力せよ。

これは基本問題です。

D – 2017-like Number

D – 2017-like Number

問題の概要

N も (N + 1) ÷ 2 も素数であるという条件を満たす奇数 N を 2017に似た数 と定義する。
Q 個のクエリで l[i], r[i] が与えられる。
l[i] 以上 r[i] 以下の 2017に似た数 となる奇数 の個数を求めよ。

ある奇数が 2017に似た数かどうかはエラトステネスの篩で得た配列を見ればわかるのですが、l[i] 以上 r[i] 以下の 2017に似た数 となる奇数 の個数をクエリのたびに数えていては時間がかかります。

そこで前処理として 10^5以下の整数について 2017 に似た数かどうかを調べるとともに sums[i] に i 以下の 2017 に似た数の個数を格納しています。クエリがきたら sums[r] – sums[l – 1] を計算することですぐに解を返せるようにしています。

D – 250-like Number

D – 250-like Number

問題の概要

k が素数 p < q であるふたつの素数を使って k = p × q^3 と表されるとき、k を「 250 に似た数」と定義する。 N 以下の 250 に似た数 は全部でいくつあるだろうか? N が 10^18 と大きいのですが、q が取りうる範囲も 10^6 なので全探索可能です。 q を全探索します。N を q^3 で割ってみてその整数部分と q - 1 を比較します。両者のうち小さいほうが p の最大値となります。 エラトステネスの篩で 10^6 以下の素数を全取得するとともに i 以下の素数の個数をすぐに求めることができるように counts[i] に i 以下の素数の個数を格納しておきます。

D – Coprime 2

D – Coprime 2

問題の概要

長さ N の正整数列 A が与えられる。
すべての要素と互いに素である 1 以上 M 以下の整数をすべて求めてください。

A のすべての要素がもたない素因数と、これらを掛け合わせて生成される合成数が出力すべき解です。

A のそれぞれの要素を素因数分解しようとすると時間がかかりそうですが、工夫すると短時間で終わります。

素因数分解は素数で割り切れる場合は割ることを繰り返す処理を繰り返しますが、時間がかかるとすれば A[i] 自体が大きな素数であるにもかかわらず、それに気づくまでに時間がかかることです。また他の素数で割ったときの商が比較的大きな素数であることも考えられます。このようなケースで落とされないために HashSet に素数を格納してすぐに処理を終わらせられるようにします。

1 から M までの整数で取得できた素因数で割ることができるかどうかの処理もひとつひとつ確認していては時間がかかるのでエラトステネスの篩のような処理をすることで時間短縮しています。

D – 9 Divisors

D – 9 Divisors

問題の概要

N 以下の正整数のうち、正の約数をちょうど 9 個持つものの個数を求めよ。

素因数分解したときに A^a × B^b × C^c × … となる自然数の約数の個数は (a + 1) × (b + 1) × (c + 1) × … です。

では (a + 1) × (b + 1) × (c + 1) × … が 9 になる場合とはどのような場合でしょうか? a = 8 のときと、a = 2, b = 2 のとき以外ありません。

a = 8 のときは比較的簡単です。エラトステネスの篩で素数を取得してそれらの 8 乗が N を超えるまで数え上げます。a = 2, b = 2 の場合は実は同じ素数の組み合わせをダブルカウントしないように 2 つの素数 p, q を p < q と考えます。素数の 2 乗のリストを生成して、すべての p について N / (p * p) 以下で (p * p) + 1 以上の個数を二分探索法で数え上げます。p が大きくなると N / (p * p) が p を下回ったり、N / (p * p) 以下で (p * p) + 1 以上の要素の個数が 0 になります。その場合はそれ以上調べても見つからないので探索を終了します。

二分探索の処理は ライブラリ ac-library-csharp の StlFunction.BinarySearch メソッドを使用しています。

参考: ac-library-csharpを使ってみる