Day 178-Q4 — 2026-10-14 | f(p^e)=p xor e の総和を強力数法で(g=φ に 2 だけ補正を掛けて g(p)=f(p) を合わせ、h を強力数の上だけで回す。min_25 篩と同じ答え・同じ速さ)

2026-10-14 赤色 Master (Phase 8+) ★★★★★★★★★ 数論・乗法的関数(焦点帯外 M3)。強力数法(Powerful Number 法)は「素数での値が同じで、前綴和が速く求まる乗法的関数 $g$」を見つける問題に帰着する。$f(p)=p\oplus1$ は奇素数で $p-1=\varphi(p)$、$p=2$ だけ $3\ne\varphi(2)$。$\varphi$ に「2 のところだけ」の補正を Dirichlet 積で掛ければ $g(p)=f(p)$ が全素数で揃う。測ってみると、律速は強力数の列挙(0.1 s)ではなく $\varphi$ の前綴和(杜教篩)だった

問題

乗法的関数 $f$ を、素数 $p$ と $e\ge1$ について $f(p^e)=p\oplus e$($\oplus$ はビットごとの XOR)、$f(1)=1$ で定める。

$$\sum_{n=1}^{N}f(n)\bmod(10^9+7)$$

を求めよ。

入力形式

N

制約

#制約
1$1\le N\le10^{10}$
2制限時間 5 秒を想定(下の計算量の注を参照)

入出力例

入力例1

10

出力例1

36

($f(1..10)=1,3,2,0,4,6,6,1,1,12$。$f(4)=2\oplus2=0$、$f(8)=2\oplus3=1$、$f(9)=3\oplus2=1$)

入力例2

100

出力例2

3106

入力例3

10000000000

出力例3

251539221

($N=10^{10}$ の値は、本解と、別に実装した min_25 篩の 2 つで一致。入力例 1・2 と $N\le3\times10^5$ の 120 個は篩による全探索とも一致)

背景と出典

  • $f(p^e)=p\oplus e$ の総和は LOJ #6053「简单的函数」と記憶している(原典は本セッションで未確認)。そこでは min_25 篩で解くのが定番だったと記憶している
  • 強力数法そのものは Day118-Q2 で扱った。杜教篩($\varphi$ の前綴和)は Day100-Q4。本問はその 2 つを組み合わせ、「$g$ をどう選ぶか」と「どちらが律速か」を測ることに焦点を置く
  • 本問の問題文・制約・入力例は自作(関数の定義だけが上の問題と同じ)

概念図

f = g * h(Dirichlet 積)。g(p) = f(p) を全素数で揃えると h は強力数の上だけ非零 φ 奇素数で p−1 = p⊕1 g = φ * u u(2)=2 だけ非零 → g(2)=3 h = f / g h(p)=0 → 強力数だけ Σf = Σ_m h(m)  · S_g(N/m)m は強力数(214122 個) N = 10¹⁰ の時間の内訳(実測、起動込み 3.56 s) 篩 φ (T≈0.5N^(2/3)):0.75 s 杜教篩 S_φ:2.00 s 強力数 DFS:0.11 s 本解と min_25 篩(実測、同じ答え) N=10¹⁰ 強力数法 3.56 s・214 MB min_25 3.86 s・57 MB N=10¹¹ 強力数法 17.46 s・923 MB min_25 19.62 s・150 MB

ヒント(クリックで段階的に開示)

ヒント1(方向性)

$f=g*h$(Dirichlet 積)と分解できて、$g(p)=f(p)$ がすべての素数で成り立つなら、$h(p)=0$ になる。乗法的な $h$ が素数で 0 なら、$h(n)\ne0$ になりうるのは強力数(素因数がすべて 2 乗以上で入っている数)だけ。$N$ 以下の強力数は $O(\sqrt N)$ 個しかない。

ヒント2(アプローチ)

$\sum_{n\le N}f(n)=\sum_{m\text{ 強力数}}h(m)\,S_g(\lfloor N/m\rfloor)$。必要なのは

  • $S_g$ が速く求まる $g$ で、$g(p)=p\oplus1$
  • 各素数 $p\le\sqrt N$ での $h(p^e)$($f=g*h$ から $h(p^e)=f(p^e)-\sum_{i\ge1}g(p^i)h(p^{e-i})$)
ヒント3(誘導)
  • 奇素数では $p\oplus1=p-1=\varphi(p)$。$p=2$ では $3=\varphi(2)+2$
  • $u(2)=2$、$u$ はそれ以外の素数べきで 0 という乗法的関数を作り、$g=\varphi*u$ とする。すると $g(2)=\varphi(2)+u(2)=3$、奇素数では $g(p)=\varphi(p)$
  • $S_g(m)=S_\varphi(m)+2S_\varphi(\lfloor m/2\rfloor)$。$S_\varphi$ は杜教篩 $S_\varphi(m)=\frac{m(m+1)}2-\sum_{d=2}^{m}S_\varphi(\lfloor m/d\rfloor)$ で、$\lfloor N/k\rfloor$ の形の値だけ求めればよい

模範解答 (Python)

模範解答を表示する(まず自力で解いてから)
import sys

MOD = 10**9 + 7


def main():
    N = int(sys.stdin.readline())
    # ---- 1) φ の前計算(篩): T ≈ 0.5·N^(2/3)(下の測定で決めた)
    T = min(N, max(100, int(0.5 * N ** (2 / 3))))
    phi = list(range(T + 1))
    comp = bytearray(T + 1)
    for p in range(2, T + 1):
        if not comp[p]:
            comp[p * p::p] = b"\x01" * len(range(p * p, T + 1, p)) if p * p <= T else b""
            for j in range(p, T + 1, p):
                phi[j] -= phi[j] // p
    pre = [0] * (T + 1)
    acc = 0
    for i in range(1, T + 1):
        acc += phi[i]
        pre[i] = acc % MOD
    # ---- 2) 杜教篩: m > T の S_φ(m) を m = N//k の k で memo
    big = {}
    inv2 = (MOD + 1) // 2

    def S_phi(m):
        if m <= T:
            return pre[m]
        k = N // m
        if k in big:
            return big[k]
        # 小さい m から順に計算すれば再帰は浅い(下で k の大きい順に呼ぶ)
        res = m % MOD * ((m + 1) % MOD) % MOD * inv2 % MOD
        d = 2
        while d <= m:
            q = m // d
            nd = m // q
            res -= (nd - d + 1) * S_phi(q)
            d = nd + 1
        res %= MOD
        big[k] = res
        return res

    for k in range(N // (T + 1), 0, -1):       # m = N//k が大きくなる順 → 再帰は 1 段で済む
        if N // k > T:
            S_phi(N // k)

    def S_g(m):                                 # g = φ * u、u(2) = 2 だけ非零 → S_g(m) = S_φ(m) + 2 S_φ(m//2)
        return (S_phi(m) + 2 * S_phi(m // 2)) % MOD

    # ---- 3) 素数 p <= √N ごとに h(p^e) を作る
    r = int(N ** 0.5)
    while r * r > N:
        r -= 1
    while (r + 1) * (r + 1) <= N:
        r += 1
    sieve = bytearray([1]) * (r + 1)
    primes = []
    for i in range(2, r + 1):
        if sieve[i]:
            primes.append(i)
            sieve[i * i::i] = bytearray(len(range(i * i, r + 1, i)))
    hcoef = []
    for p in primes:
        pe = [1]
        while pe[-1] <= N // p:
            pe.append(pe[-1] * p)
        E = len(pe) - 1
        if p == 2:
            g = [1] + [3 if e == 1 else pe[e] for e in range(1, E + 1)]
        else:
            g = [1] + [pe[e] - pe[e - 1] for e in range(1, E + 1)]
        h = [1, 0]
        for e in range(2, E + 1):
            v = p ^ e
            for i in range(1, e + 1):
                v -= g[i] * h[e - i]
            h.append(v % MOD)
        hcoef.append(h)
    # ---- 4) 強力数を DFS で列挙し Σ h(n) S_g(N//n)
    ans = S_g(N)
    stack = [(1, 1, 0)]                         # (n, h(n), 次に使える素数の添字)
    while stack:
        n, hn, i = stack.pop()
        while i < len(primes):
            p = primes[i]
            if n * p * p > N:
                break
            h = hcoef[i]
            m = n * p * p
            e = 2
            while m <= N:
                hv = hn * h[e] % MOD
                if hv:
                    ans += hv * S_g(N // m)
                    stack.append((m, hv, i + 1))
                m *= p
                e += 1
            i += 1
    print(ans % MOD)


main()

計算量: 篩 $O(T\log\log T)$、杜教篩 $O(N/\sqrt T)$、強力数 $O(\sqrt N)$ 個。$T\approx N^{2/3}$ で全体 $O(N^{2/3})$。$N=10^8$: 0.15 s、$10^9$: 0.67 s、$10^{10}$: 3.56 s・214 MB、$10^{11}$: 17.46 s・923 MB(Python 3.14.8、Apple M5 Pro、負荷平均 12〜20 での実測、起動込み)。$N=10^{10}$ の内訳は篩 0.75 s、杜教篩 2.0 s、$h$ の表 0.03 s、強力数の DFS 0.11 s(実測)。2 秒には収まらないので制限時間 5 秒を想定とした。

Step-by-Step 解説

1なぜ強力数だけでよいか

$f=g*h$ で $f(p)=g(p)h(1)+g(1)h(p)=g(p)+h(p)$。$g(p)=f(p)$ なら $h(p)=0$。$h$ は乗法的なので、素因数 $p$ がちょうど 1 乗で入る $n$ では $h(n)=0$。残るのは強力数だけで、$N=10^{10}$ 以下に 214122 個(1 を含む、実測)。

2$g$ の選び方

$f(p)=p\oplus1$ は「$p$ が奇数なら最下位ビットを落とす $=p-1$」「$p=2$ なら $3$」。$\varphi(p)=p-1$ が奇素数で一致するので、2 だけ直す。$g=\varphi*u$、$u(2)=2$(他の素数べきで 0)とすると

  • $g(2)=\varphi(2)+2\varphi(1)=3$、$g(2^e)=\varphi(2^e)+2\varphi(2^{e-1})=2^e$($e\ge2$)
  • 奇素数べきでは $g(p^e)=\varphi(p^e)$
  • 前綴和は $S_g(m)=\sum_{k}u(k)S_\varphi(\lfloor m/k\rfloor)=S_\varphi(m)+2S_\varphi(\lfloor m/2\rfloor)$

$\lfloor\lfloor N/n\rfloor/2\rfloor=\lfloor N/(2n)\rfloor$ なので、要る値はすべて $\lfloor N/k\rfloor$ の形に収まる。

3$h(p^e)$

$f(p^e)=\sum_{i=0}^{e}g(p^i)h(p^{e-i})$ を $h(p^e)$ について解く。$p=2$ で $h=1,0,-4,5,-9,14,\dots$、$p=3$ で $1,0,-5,-8,-1,\dots$(実測)。負の値は法を取って持つ。

4杜教篩

$\sum_{d\mid n}\varphi(d)=n$ から $\sum_{d=1}^{m}S_\varphi(\lfloor m/d\rfloor)=\frac{m(m+1)}2$。$m\le T$ は篩の前綴和、$m>T$ は $k=\lfloor N/m\rfloor$ を鍵に memo。$k$ の大きい順($m$ の小さい順)に呼ぶと、右辺の $S_\varphi(\lfloor m/d\rfloor)$ はすでに計算済みで再帰が 1 段で終わる。

5$T$ を測って決める

理論上の最適は $T\approx N^{2/3}$ だが、Python では篩(リストの書き換え)と杜教篩(整数の割り算の繰り返し)の定数が違う。$N=10^{10}$ で $T=c\cdot N^{2/3}$ の $c$ を振ると、$c=0.15$: 4.49 s、$0.3$: 3.71 s、$0.5$: 3.48 s、$1.0$: 4.06 s(実測)。$c=0.5$ を採った。

迷った他案と採らなかった理由

案計算量採否理由
線形篩で $f$ を全部作る$O(N)$検算用のみ$N=10^{10}$ では不可能
min_25 篩(素数の個数と和を Lucy 法で、合成数を再帰で)$O(N^{3/4}/\log N)$比較用に実装答えは一致。$10^{10}$ で 3.86 s・57 MB、$10^{11}$ で 19.62 s・150 MB(実測)。速さは本解とほぼ同じで、メモリは本解の 1/4〜1/6
$g=\varphi$ のまま強力数法$O(N^{2/3})$不採用(誤り)$h(2)=2\ne0$ になり、$h$ が強力数以外にも乗る。$N=1..300$ で 299 個誤る(実測。正しいのは $N=1$ だけ)
強力数法+杜教篩(本解)$O(N^{2/3})$採用3.56 s。$g$ の選び方と律速の位置が目に見える

前提が変わったら

  • メモリが 256 MB 未満なら min_25 篩を選ぶ($10^{10}$ で 57 MB 対 214 MB)
  • $S_g$ が閉じた式で求まる $g$(例: $f(p)=p$ なら $g=\mathrm{id}$、$S_g=m(m+1)/2$)なら、律速の杜教篩が消えて強力数の DFS($10^{10}$ で 0.11 s)だけになり、強力数法が圧勝する。本問の $f(p)=p-1$ は $\varphi$ が要るので、その利点が出ない
  • $N=10^{11}$ 以上ではどちらも Python では 17〜20 s。C++ で書くか、$\varphi$ の前綴和を事前計算の表から読む必要がある

よくあるミス

ミス原因正しい書き方
$g=\varphi$ で済ませる$f(2)=3$ を見落とす($2\oplus1=3$ で $p-1$ ではない)2 だけ補正した $g=\varphi*u$
$f(p^e)$ を $p^e\oplus e$ と読む定義の読み違い$p\oplus e$。$f(4)=0$、$f(8)=1$
$S_g(\lfloor N/n\rfloor)$ の $\lfloor\cdot/2\rfloor$ を別の値として memo する鍵の形を揃えない$\lfloor N/(2n)\rfloor$ も $\lfloor N/k\rfloor$ の形。$k=\lfloor N/m\rfloor$ を鍵にする
杜教篩を $m$ の大きい順に再帰で呼ぶ再帰が深くなり Python の上限に当たる$k$ の大きい順($m$ の小さい順)に先に埋める
$h(p^e)$ を法を取らずに持つ負の値を許すつもりで巨大化毎回 $\bmod$ を取る

次のステップ

  • 発展1: $f(p^e)=p^e\oplus e$ のように、$f(p)$ が $p$ の多項式で書けない関数で強力数法が使えるかを考える
  • 発展2: min_25 篩の Lucy 部分だけを本解の $S_\varphi$ の代わりに使い($S_g$ を素数和から作る道)、どちらの組み合わせが速いか測る
  • 発展3: 同じ $N$ で 1000 個の $f$($f(p^e)=p\oplus(e+c)$ など)を一度に答える場合、杜教篩を共有して何倍速くなるか

検証ログ(本セッションで実行した数値)

  • 正しさ(実測): $N=1..59$、乱数 60 個($60\le N\le3\times10^5$)、$N=3\times10^5$ の計 120 個で、最小素因数の篩で $f$ を全部作った全探索と一致($N=3\times10^5$ の値 396335019)。$N=10,100,3\times10^5,10^8,10^9,10^{10},10^{11}$ で min_25 篩と一致
  • 誤った方法(実測): $g=\varphi$ のままでは $N=1..300$ の 299 個で誤る
  • 時間とメモリ(実測、起動込み、負荷平均 12〜20):
$N$本解min_25 篩
$10^8$0.15 s・25 MB0.15 s・20 MB
$10^9$0.67 s・59 MB0.73 s・29 MB
$10^{10}$3.56 s・214 MB3.86 s・57 MB
$10^{11}$17.46 s・923 MB19.62 s・150 MB
  • 内訳(実測、$N=10^{10}$): 篩 0.75 s、杜教篩 2.0 s(memo 4308 個)、強力数の DFS 0.11 s(強力数 214121 個+1)
  • 扱っていない範囲: 原典(LOJ)の確認、C++ での比較、numpy を使った篩(この環境の Python 3.14 には numpy が無かった)

自己評価

自分の回答

気づき・メモ