樱花

GitHub跳转原题关系图返回列表

把方程变形为 (x−n!)(y−n!)=(n!)²,答案即 (n!)² 的约数个数:线性筛最小质因子分解 1..n 累加指数后连乘 ∏(2e+1)。

OJ: roj

题目 ID: 1624

难度:普及+/提高-

标签:

创建: 2026-09-30 22:43

更新: 2026-10-07 13:50

形式化题目

给定正整数 nn(1⩽n⩽1061 \leqslant n \leqslant 10^6),统计满足

1x+1y=1n!\frac{1}{x} + \frac{1}{y} = \frac{1}{n!}

的正整数对 (x,y)(x, y) 的数目,答案对 109+710^9 + 7 取模。

样例:n=2n = 2 时 n!=2n! = 2,共有 (3,6),(4,4),(6,3)(3,6),(4,4),(6,3) 三组解,答案为 33。

这道题的关键在于:x,yx, y 没有任何上界,直接枚举 xx 或 yy 都不可行——必须先把方程变成一个"能数"的结构。

正解

思路

第一步:把方程变形成乘积式

设 N=n!N = n!。方程两边同乘 xyNxyN:

Ny+Nx=xy.Ny + Nx = xy.

把左边整体移到右边,再"凑"一个 N2N^2:

xy−Nx−Ny+N2=N2  ⟹  (x−N)(y−N)=N2.xy - Nx - Ny + N^2 = N^2 \implies (x - N)(y - N) = N^2.

这一步把加法方程变成了乘积方程,是数方程计数的经典手法(和 x−1+y−1=1x^{-1} + y^{-1} = 1 类问题通用)。

第二步:解的个数 = N2N^2 的约数个数

若 (x,y)(x,y) 是正整数解,由 xy=N(x+y)>Nxxy = N(x+y) > Nx 得 y>Ny > N,同理 x>Nx > N,于是 x−Nx - N 和 y−Ny - N 都是正整数,且乘积为 N2N^2。

反过来,取 N2N^2 的任意正因子 dd,令

x=N+d,y=N+N2d,x = N + d, \qquad y = N + \frac{N^2}{d},

则 (x−N)(y−N)=d⋅N2d=N2(x-N)(y-N) = d \cdot \frac{N^2}{d} = N^2,代回原方程成立,且 x,y>N⩾1x, y > N \geqslant 1 为正整数。

所以"正整数解 (x,y)(x,y)"与"N2N^2 的有序因子对 (d1,d2)(d_1, d_2)(d1d2=N2d_1 d_2 = N^2)"一一对应,答案就是

d((n!)2).d\bigl((n!)^2\bigr).

用样例 n=2n=2 验证一下这个双射:N=2N = 2,N2=4N^2 = 4 的三个约数 1,2,41, 2, 4 恰好对应三组解:

约数 d=x−Nd = x-N x=N+dx = N+d y=N+4/dy = N + 4/d 解
11 33 2+4=62 + 4 = 6 (3,6)(3, 6)
22 44 2+2=42 + 2 = 4 (4,4)(4, 4)
44 66 2+1=32 + 1 = 3 (6,3)(6, 3)

第三步:计算 d((n!)2)d((n!)^2)——只需要 n!n! 的质因数分解

N2N^2 可能是一个天文数字(106!10^{6!} 级别),但约数个数只由质因数分解决定:

若 n!=∏pepn! = \prod p^{e_p},那么 (n!)2=∏p2ep(n!)^2 = \prod p^{2 e_p},由约数个数公式

d((n!)2)=∏p⩽n(2ep+1).d\bigl((n!)^2\bigr) = \prod_{p \leqslant n} \bigl(2 e_p + 1\bigr).

所以整个问题坍缩成一件事:求出 n!n! 里每个质数 p⩽np \leqslant n 的指数 epe_p。

第四步:线性筛 + 逐数分解

epe_p 有两条等价的求法:

  • Legendre 公式:ep=∑k⩾1⌊n/pk⌋e_p = \sum_{k \geqslant 1} \lfloor n / p^k \rfloor;
  • 逐数分解:把 2,3,…,n2, 3, \dots, n 每个数分解,指数累加。

两者复杂度同阶。这里选逐数分解,配合线性筛的最小质因子:筛出每个合数的最小质因子 min_prime[v]\mathrm{min\_prime}[v],之后分解任何一个 vv 只需要不断"除以自己的最小质因子",无需试除。把每个数分解出的指数累加进 exp[p],就得到了 n!n! 的标准分解。

这里有一个容易踩的坑:必须先把所有 vv 的指数累加得到 n!n! 的总指数 epe_p,再对每个质数乘上 2ep+12 e_p + 1。如果对每个 vv 分解出一个质因子就立刻乘 (2e+1)(2e+1),那就变成了 ∏vv2\prod_v v^2 的因子结构错位,结果完全不对。

代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 22:43
# update_at: 2026-09-30 22:43

from math import prod
from sys import stdin

MOD = 10**9 + 7


def solve() -> None:
    n = int(stdin.buffer.read())

    # 线性筛:只记录每个合数的最小质因子,其余因数分解全靠它跳转。
    min_prime = [0] * (n + 1)
    primes: list[int] = []
    for v in range(2, n + 1):
        if min_prime[v] == 0:              # v 没有更小的质因子 → v 是质数
            min_prime[v] = v
            primes.append(v)
        for p in primes:
            if p > min_prime[v] or p * v > n:
                break
            min_prime[p * v] = p

    # 先把 2..n 每个数的质因子指数累加起来,得到 n! 的标准分解。
    exp = [0] * (n + 1)
    for v in range(2, n + 1):
        while v > 1:
            p = min_prime[v]
            while v % p == 0:              # 除尽同一个质因子
                v //= p
                exp[p] += 1

    # ans = d((n!)^2):n! 里指数为 E 的质因子,平方后指数 2E,贡献 2E+1。
    print(prod(2 * e + 1 for e in exp) % MOD)


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-05 23:10
 * update_at: 2026-10-05 23:12
 */
// main.cpp:正解。把 1/x + 1/y = 1/n! 变形为 (x-n!)(y-n!)=(n!)^2,
// 正整数解数目 = (n!)^2 的约数个数,用线性筛分解 1..n 求质因子指数后连乘。
#include <iostream>

typedef long long ll;

const int MAXN = 1000000 + 5; // n 的上界
const ll MOD = 1000000007;

int min_prime[MAXN]; // min_prime[v] = v 的最小质因子,线性筛中顺带求出
int primes[MAXN]; // primes[1..prime_cnt] 存放筛出的质数
int prime_cnt;
ll exp_of_prime[MAXN]; // exp_of_prime[p] = p 在 n! 中的指数

int n;

// 线性筛:每个合数只被它的最小质因子筛掉一次,同时记录最小质因子。
void linear_sieve() {
    for (int v = 2; v <= n; v++) {
        if (min_prime[v] == 0) { // 没有更小的质因子,说明 v 是质数
            min_prime[v] = v;
            prime_cnt++;
            primes[prime_cnt] = v;
        }
        for (int i = 1; i <= prime_cnt; i++) {
            int p = primes[i];
            if (p > min_prime[v] || p > n / v) { // p * v > n 时停止
                break;
            }
            min_prime[p * v] = p;
        }
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    std::cin >> n;

    linear_sieve();

    // 把 2..n 每个数分解质因数,指数累加得到 n! 的标准分解。
    for (int i = 2; i <= n; i++) {
        int x = i; // 用临时变量分解,不能改动外层循环变量 i
        while (x > 1) {
            int p = min_prime[x];
            while (x % p == 0) {
                x /= p;
                exp_of_prime[p]++;
            }
        }
    }

    // 答案 = d((n!)^2) = ∏ (2 * e_p + 1),其中 e_p 是 p 在 n! 中的指数。
    ll answer = 1;
    for (int i = 1; i <= prime_cnt; i++) {
        int p = primes[i];
        answer = answer * (2 * exp_of_prime[p] + 1) % MOD;
    }

    std::cout << answer << "\n";
    return 0;
}

复杂度

  • 时间:线性筛 O(n)O(n),逐数分解总代价为 ∑v⩽nω(v)\sum_{v \leqslant n} \omega(v)(ω\omega 是质因子个数),为 O(nlog⁡log⁡n)O(n \log \log n) 级;合计 O(nlog⁡log⁡n)O(n \log \log n),n=106n = 10^6 时 C++ 轻松通过,Python 实现也在数秒内完成。
  • 空间:两个长度 nn 的整数数组,O(n)O(n)。
  • 取模注意:指数 epe_p 本身不超过 ∑⌊n/pk⌋\sum \lfloor n/p^k \rfloor 量级(约 2.5×1072.5 \times 10^7,即 2e+12e+1 很小),不需要对指数取模,只对乘积取模。

总结

  • 核心变形:1x+1y=1N  ⟺  (x−N)(y−N)=N2\frac{1}{x} + \frac{1}{y} = \frac{1}{N} \iff (x-N)(y-N) = N^2,其中 N=n!N = n!。
  • 由 x,y>Nx, y > N 知解与 N2N^2 的有序因子对一一对应,答案 =d((n!)2)=∏p⩽n(2ep+1)= d\bigl((n!)^2\bigr) = \prod_{p \leqslant n} (2e_p + 1)。
  • 实现上只需线性筛最小质因子,把 2..n2..n 逐数分解累加指数,最后连乘 (2ep+1)(2 e_p + 1) 取模。
  • 这道题的通用套路:“倒数和 = 1” 型方程计数 → 移项凑平方 → 约数个数 → 质因数分解(配合 Legendre / 线性筛)。