G - 250-like Number Editorial by cirno3153


公式解説の方法では \(pq^3\) の値が64bit整数で収まらない可能性があり、やや面倒です。 代わりに、次のような解法を使えばより簡単な方針で解くことができます。

  • まず、素数表から \(q\) にあたる整数を決め打つ。
  • 次に、条件を満たす \(p\) の値を考える。これは、 \([1, \min(q-1, \frac{N}{q^3})]\) を満たす素数全てである。
  • ここで、\([1, \min(q-1, \frac{N}{q^3})]\) を満たす素数の個数は、素数の個数の累積和を用いれば \(O(1)\) で計算できる。
  • 以上の流れを \(N^{\frac{1}{3}}\) 以下の全ての素数 \(q\) に対して行う。

素数の整数性から、 \(\frac{N}{q^3}\)は端数を切り捨てても構いません(つまり、整数型で扱っても問題ありません)。 これにより、演算中の全ての値が \(N\) 以下に収まります。

実装例(C++):

#include<algorithm>
#include<cmath>
#include<iostream>
#include<vector>
/**
max以下の素数を昇順に列挙します。
計算量はO(max)です。
制約: 0≦max≦10^9
*/
std::vector<int> getPrimes(int max) {
  std::vector<int> primes;
  std::vector<int> lpf(max + 1, 0);
  for (int i = 2;i <= max;++ i) {
    if (lpf[i] == 0) {
      lpf[i] = i;
      primes.push_back(i);
    }
    for (int p : primes) {
      int mul = p * i;
      if (mul > max || p > lpf[i]) break; // ベルトラン=チェビシェフの定理より、p*iはmaxの2倍以下
      lpf[mul] = p;
    }
  }
  return primes;
}

int main() {
  long long N;
  std::cin >> N;
  int cbrtN = std::cbrt(N);
  std::vector<int> primes = getPrimes(cbrtN);
  std::vector<int> cumsum(1, 0); // cumsum[i]はi以下の素数の個数を持つ
  cumsum.reserve(cbrtN + 1);
  for (int i = 1, pi = 0, p = 2;i <= cbrtN;++ i) {
    int last = cumsum[cumsum.size() - 1];
    if (i == p) {
      ++ last;
      ++ pi;
      p = pi >= (int)primes.size() ? cbrtN + 1 : primes[pi];
    }
    cumsum.push_back(last);
  }
  long long ans = 0;
  for (int q : primes) {
    int pMax = (int)std::min(q - 1LL, N / q / q / q);
    ans += cumsum[pMax];
  }
  std::cout << ans << std::endl;
}

posted:
last update: