Official

E - LCM Sequence Editorial by Nyaan


\(A_n\) は単調に増加する数列です。よってこの問題の答えは

  • \(1+\) (\(L+1 \leq i \leq R\) を満たす整数 \(i\) のうち \(A_{i-1} \lt A_i\) であるものの個数)

が答えとなります。(はじめの \(1\)\(A_L\) の分)

\(A_{i-1} \lt A_i\) となる \(A_i\) がどこになるかを考えましょう。
\(A_n = \mathrm{LCM}(1,2,\dots,n)\) です。ここで \(A_n\)\(p\) で何回割れるかを考えると、\(1,2,\dots,n\) の中で \(p\) で割れる回数が最も多いものは

  • \(p^k \leq n\) (\(k\) は整数) を満たす最大の \(p^k\)

となり、割れる回数は \(k\) になることから、\(A_n\) もまた(上記の条件を満たす \(k\) を用いて) \(p\)\(k\) 回割れることになります。

よって、\(n\) がある素数 \(p\) を用いて \(p\) の冪乗として表せる(これを 素数冪 と呼びます)とき、\(A_n\)\(A_{n-1}\) に比べて \(p\)\(1\) 回多く割れることになります。\(p\) 以外の素数において割れる回数は \(A_n\)\(A_{n-1}\) で変わらないので、\(A_n\)\(A_{n-1}\)\(p\) 倍であることが言えます。
逆に \(n\) が素数冪でない場合、任意の素数 \(p\) について \(p\) で割れる回数は \(A_n\)\(A_{n-1}\) で変わらないことがわかります。よってこの場合は \(A_n = A_{n-1}\) です。

以上の議論より、この問題の答えは

  • \(1+\) (\(L+1 \leq i \leq R\) を満たす整数 \(i\) のうち素数冪であるものの個数)

になることがわかりました。
整数 \(L+1, \dots, R\) を全て素因数分解する処理は 区間篩 と呼ばれるアルゴリズムを用いて \(M = \max(R-L, \sqrt{R})\) として \(\mathrm{O}(M \log \log M)\) で計算できることが知られています。このアルゴリズムは過去にも出題されているので 過去の問題の解説 を参照してください。
以上よりこの問題を \(M = \max(R-L, \sqrt{R})\) として \(\mathrm{O}(M \log \log M)\) で解くことができて、これは十分高速です。

  • 実装例(C++)
#include <cmath>
#include <iostream>
#include <vector>
using namespace std;

vector<int> prime_enumerate(int N) {
  vector<bool> is_prime(N + 1, true);
  vector<int> primes;
  if (N < 2) return primes;
  is_prime[0] = is_prime[1] = false;
  for (int i = 2; i * i <= N; ++i) {
    if (is_prime[i]) {
      for (int j = i * i; j <= N; j += i) is_prime[j] = false;
    }
  }
  for (int i = 2; i <= N; ++i) {
    if (is_prime[i]) primes.push_back(i);
  }
  return primes;
}

int main() {
  long long L, R;
  cin >> L >> R;
  vector<int> vis(R - L);
  int ans = 1;
  for (int p : prime_enumerate(sqrt(R) + 100)) {
    for (long long x = (L / p + 1) * p; x <= R; x += p) {
      if (vis[x - (L + 1)]) continue;
      vis[x - (L + 1)] = 1;
      long long y = x;
      while (y % p == 0) y /= p;
      if (y == 1) ans++;
    }
  }
  for (int v : vis) ans += v == 0;
  cout << ans << "\n";
}

posted:
last update: