ほどよい全探索問題! 約数列挙のアルゴリズムなどをちゃんと理解していれば解ける!
問題概要
以下の正整数のうち、
なる素数
を用いて
と表せるものはいくつあるか?
制約
前提知識
この問題を見てまったく見当もつかないという場合には、「約数列挙」アルゴリズムをまず勉強するとよさそう!!
次の記事の 3 章までを勉強すると、今回のこの問題にも手がつけられるようになると思う!
簡単に復習してみる。
正整数 の約数をすべて求めるアルゴリズムの肝は「
まで試せば十分」という点にある。その理由が大切だ。
を
の約数としたとき、整数
を用いて
と表せる。ここで、 であると仮定しよう (そうでない場合も同様に求められる)。このとき、
となるので、 となる。よって、
を
から
まで順に試せば良いことがわかった。計算量は
となる。
今回の問題でも
今回の問題も、基本的には全探索アルゴリズムで解ける。このとき、 をどこまで試せばよいかが肝となる。
の範囲を絞る
より、
となります。このことから、
とわかります。ざっくり「一番小さい数は十分小さくなるはず」と信じて、不等式評価する感じですね。 なので、
程度まで試せば十分だとわかります。
の範囲を絞る
次に を固定したときの
の範囲を考えます。最悪の場合として
のときを考えることにしましょう (1 は素数ではないですが......)。このとき、
となります。このことから、
とわかります。 なので、
まで試せば十分だとわかります。
の個数を求める
最後に、 を固定したとき、
を満たす素数 の個数を考えます。つまり、
を満たすような素数 の個数を求めれば良いです。これは
- あらかじめ、エラトステネスの篩を用いて、各整数が素数かどうかを調べておいて
- その累積和をとっておくことで
の計算量で求められます。
このことがピンと来ない場合には、次の記事の「問題 2: AtCoder ABC 084 D - 2017-like Number」のところまで読めば、納得できるはずです!
なお、 なので、
となります。 より、
以下の整数について、エラトステネスの篩を適用すれば十分です。
おまけ:正確な計算量
正確な計算量を見積もってみます。
- 最初にエラトステネスの篩を適用する部分:
- その累積和を求める部分:
を全探索する部分:
となります。つまり全体の計算量は となります。
コード
#include <bits/stdc++.h> using namespace std; // エラトステネスの篩 vector<bool> Era(int n) { vector<bool> isprime(n, true); isprime[0] = false; isprime[1] = false; for (int i = 2; i < n; ++i) isprime[i] = true; for (int i = 2; i < n; ++i) { if (isprime[i]) { for (int j = i*2; j < n; j += i) isprime[j] = false; } } return isprime; } int main() { long long N; cin >> N; // 10^6 以下の整数が素数かどうかを求めておく const int MAX = 1100000; vector<bool> isprime = Era(MAX); // l 以上 r 未満の素数の個数を数えられるようにするための累積和 vector<long long> S(MAX + 1, 0); for (int i = 0; i < MAX; ++i) S[i+1] = S[i] + isprime[i]; // 数える long long res = 0; for (long long a = 1; a * a * a * a * a <= N; ++a){ if (!isprime[a]) continue; for (long long b = a + 1; b * b * b <= N; ++b) { if (!isprime[b]) continue; // b+1 以上 N/a^2b 以下の素数の個数を足す long long cmax = sqrt(N/a/a/b); if (b + 1 <= cmax + 1) res += S[cmax + 1] - S[b + 1]; } } cout << res << endl; }