Day 068-Q3 — 乗法的関数・線形篩(Euler の φ + Möbius の μ + 約数和 σ)

2026-06-21 赤色 Master / Phase 8+ ★★★★★★★★★ 線形篩・乗法的関数・メビウス反転

問題

$T$ 個のテストケースが与えられる。各テストケースの $N$ に対して次の3つの総和を出力せよ(mod $10^9+7$):

  • $\displaystyle \sum_{i=1}^{N} \phi(i)$ — オイラーの φ 関数の総和
  • $\displaystyle \sum_{i=1}^{N} |\mu(i)|$ — メビウス関数の絶対値の総和(平方因子を持たない整数の個数)
  • $\displaystyle \sum_{i=1}^{N} \sigma(i)$ — 約数和関数の総和

制約

パラメータ範囲
$T$$1 \le T \le 10$
$N$$1 \le N \le 10^7$

入出力例

入力例 1

2
10
100

出力例 1

32 7 87
3044 61 8299

N=10: φ総和=32(1+1+2+2+4+2+6+4+6+4)、|μ|=7(1,2,3,5,6,7,10のみ)、σ総和=87。

概念図: 線形篩の乗法的関数更新規則

線形篩: m = n × p における更新(p = lpf[m]) 場合1: p < lpf[n] (p ∤ n) gcd(p, n) = 1 なので乗法的性質が使える φ(m) = φ(n) × (p - 1) μ(m) = μ(n) × (-1) σ(m) = σ(n) × (p + 1) ← f(m) = f(n) × f(p) をそれぞれ適用 場合2: p = lpf[n] (p | n) n = p^e × k (k と p は互いに素) φ(m) = φ(n) × p (p^(e+1) のφ = p^e × (p-1) × p / (p-1)) μ(m) = 0 (p^2 | m なので平方因子あり) σ(m) = σ(n/pe[n]) × σ(pe[m]) ← pe 配列で p^e を追跡しながら計算 素数 p における基本値 φ(p) = p - 1 (1から p-1 の中で gcd(k,p)=1 は全部) μ(p) = -1 (素数は平方因子なし・素因数1個なので (-1)^1) σ(p) = p + 1 (約数は 1 と p のみ) φ(1) = μ(1) = σ(1) = 1 (乗法的関数は f(1) = 1) 線形篩の利点: 各合成数は 最小素因数で1回だけふるわれる → O(N) で全値を計算

ヒント(段階的開示)

ヒント1: 方向性
$\phi$、$\mu$、$\sigma$ はいずれも乗法的関数($\gcd(m,n)=1$ なら $f(mn)=f(m)f(n)$)。線形篩で素数列挙と同時に $O(N)$ で全ての値を計算し、累積和でクエリに $O(1)$ 回答する。
ヒント2: アプローチ
条件φ(n)μ(n)σ(n)
n = 1111
n = p(素数)p-1-1p+1
p < lpf[n] (p∤n)φ(n)×(p-1)μ(n)×(-1)σ(n)×(p+1)
p = lpf[n] (p|n)φ(n)×p0σ(n/pe[n])×σ(pe[n]×p)
ヒント3: コード骨格
phi = [0]*N; phi[1] = 1
mu  = [0]*N; mu[1]  = 1
sig = [0]*N; sig[1] = 1
lpf = [0]*N  # lowest prime factor
pe  = [0]*N  # p^e where p^e || n
primes = []

for n in range(2, N):
    if lpf[n] == 0:  # n is prime
        lpf[n] = pe[n] = n
        phi[n] = n-1; mu[n] = -1; sig[n] = n+1
        primes.append(n)
    for p in primes:
        if p > lpf[n] or n*p >= N: break
        m = n*p; lpf[m] = p
        if p < lpf[n]:  # gcd(p,n)=1
            pe[m] = p
            phi[m] = phi[n]*(p-1)
            mu[m]  = -mu[n]
            sig[m] = sig[n]*(p+1)
        else:  # p == lpf[n]
            pe[m] = pe[n]*p
            phi[m] = phi[n]*p
            mu[m]  = 0
            sig[m] = sig[n//pe[n]] * (sig[pe[n]]*p+1)

模範解答 (Python)

import sys
input = sys.stdin.readline

MOD = 10**9 + 7

def build(N):
    phi = [0] * N
    mu  = [0] * N
    sig = [0] * N
    lpf = [0] * N
    pe  = [0] * N

    phi[1] = mu[1] = sig[1] = 1
    primes = []

    for n in range(2, N):
        if lpf[n] == 0:
            lpf[n] = pe[n] = n
            phi[n] = n - 1
            mu[n]  = -1
            sig[n] = n + 1
            primes.append(n)
        for p in primes:
            if p > lpf[n] or n * p >= N:
                break
            m = n * p
            lpf[m] = p
            if p < lpf[n]:
                pe[m]  = p
                phi[m] = phi[n] * (p - 1)
                mu[m]  = -mu[n]
                sig[m] = sig[n] * (p + 1)
            else:
                pe[m]  = pe[n] * p
                phi[m] = phi[n] * p
                mu[m]  = 0
                # sigma(p^(e+1)) = sigma(p^e)*p + 1
                sig_pe_new = sig[pe[n]] * p + 1
                sig[m] = sig[n // pe[n]] * sig_pe_new

    return phi, mu, sig

def main():
    T = int(input())
    Ns = [int(input()) for _ in range(T)]
    maxN = max(Ns) + 1

    phi, mu, sig = build(maxN)

    phi_pre = [0] * maxN
    mu_abs_pre = [0] * maxN
    sig_pre = [0] * maxN
    for i in range(1, maxN):
        phi_pre[i] = (phi_pre[i-1] + phi[i]) % MOD
        mu_abs_pre[i] = (mu_abs_pre[i-1] + (1 if mu[i] != 0 else 0)) % MOD
        sig_pre[i] = (sig_pre[i-1] + sig[i]) % MOD

    out = []
    for N in Ns:
        out.append(f"{phi_pre[N]} {mu_abs_pre[N]} {sig_pre[N]}")
    print('\n'.join(out))

main()

Step-by-Step 解説

Step 1: 線形篩の基本

各合成数 $n$ を「最小素因数 $p = \text{lpf}[n]$」によって一度だけ生成する。内側ループで $p > \text{lpf}[n]$ になったら break することで、各合成数が最小素因数でちょうど1回生成されることを保証する。

Step 2: pe 配列の役割

pe[n] は $n$ の最小素因数 $p$ の最大冪 $p^e$($p^e \| n$)を保持する。これにより $n = p^e \times k$ と分解でき、$\sigma(n) = \sigma(k) \times \sigma(p^e)$ を使って更新できる。

Step 3: σ関数の特殊ケース

$\sigma(p^{e+1}) = \sigma(p^e) \times p + 1$(等比数列の和: $1 + p + \cdots + p^{e+1}$)。これを利用して sig[m] = sig[n//pe[n]] * (sig[pe[n]]*p + 1) で計算。

Step 4: 計算量

処理計算量
線形篩全体$O(N)$
累積和構築$O(N)$
各クエリ$O(1)$
全体(T クエリ)$O(N + T)$

よくあるミス

ミス原因正しい書き方
pe の更新タイミングp が lpf[n] と等しいケースを見落とすpe[m] = pe[n] * p で更新
σ の更新式が複雑sig(p^e * k) の分解を忘れるsig[n//pe[n]] * (sig[pe[n]]*p+1)
|μ| の集計でμを直接使うμ は -1, 0, 1 を取る1 if mu[i] != 0 else 0
配列サイズ不足n*p が N を超えるチェック漏れif n * p >= N: break を先に確認

次のステップ

発展問題: Dirichlet 畳み込み $\sum_{d|n} f(d)g(n/d)$ の高速計算(乗法的関数の積が乗法的)。メビウス反転公式の応用(GCD総和・LCM総和問題)。

自己評価