「一本通 6.3 例 1」反素数 Antiprime

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

通过质因子指数单调不增性质剪枝,用 DFS 搜索不超过 n 的约数最多且数值最小的反素数。

OJ: roj

题目 ID: 1625

难度:提高+/省选-

标签:数论搜索剪枝

创建: 2026-09-30 22:42

更新: 2026-10-07 13:50

形式化题目

对于正整数 xx,记 d(x)d(x) 为 xx 的正约数个数。若对所有满足 1⩽y<x1 \leqslant y < x 的正整数 yy,均有 d(y)<d(x)d(y) < d(x),则称 xx 为反素数(Antiprime)。

给定正整数 nn(1⩽n⩽2×1091 \leqslant n \leqslant 2 \times 10^9),求出满足 x⩽nx \leqslant n 的最大反素数 xx。

正解

思路

1. 问题等价转化

反素数要求任何严格小于它的数,约数个数都严格小于它。由此可得出以下两条关键性质:

  1. 约数个数最多:在区间 [1,n][1, n] 内,不存在任何数的约数个数严格大于最终所求反素数。若存在某个 y⩽ny \leqslant n 使得 d(y)>d(x)d(y) > d(x),则在区间 [x+1,n][x+1, n] 中必然能找到约数个数超过 d(x)d(x) 的数,与 xx 是不超过 nn 的最大反素数矛盾。
  2. 相同约数个数取最小:若有多个数的约数个数相同且达到最大值,反素数必然是其中数值最小的那一个。因为如果选了较大的数 z>xz > x 且 d(z)=d(x)d(z) = d(x),那么根据定义,存在比 zz 小的数 xx 使得 d(x)⩾d(z)d(x) \geqslant d(z),则 zz 本身就不是反素数。

因此,求不超过 nn 的最大反素数,等价于在 [1,n][1, n] 中寻找约数个数最多、且数值尽可能小的数。

2. 质因子与指数单调性

根据算术基本定理,任何正整数 xx 都可以唯一分解为若干质因子的乘积:

x=∏i=1kpici=2c1⋅3c2⋅5c3⋯pkckx = \prod_{i=1}^k p_i^{c_i} = 2^{c_1} \cdot 3^{c_2} \cdot 5^{c_3} \cdots p_k^{c_k}
其正约数个数公式为:
d(x)=∏i=1k(ci+1)=(c1+1)(c2+1)⋯(ck+1)d(x) = \prod_{i=1}^k (c_i + 1) = (c_1 + 1)(c_2 + 1)\cdots(c_k + 1)

观察约数个数公式可以发现:约数个数只与指数多重集 {ci}\{c_i\} 有关,与具体的质数底数无关。 若存在 i<ji < j(即质数 pi<pjp_i < p_j)使得指数 ci<cjc_i < c_j,我们只需交换指数 cic_i 与 cjc_j,构成新数:

x′=x⋅(pipj)cj−cix' = x \cdot \left(\frac{p_i}{p_j}\right)^{c_j - c_i}
由于 pi<pjp_i < p_j 且 cj−ci>0c_j - c_i > 0,必有 x′<xx' < x。 同时 d(x′)=d(x)d(x') = d(x)。也就是说,我们找到了一个严格更小的数 x′x',其约数个数与 xx 完全相同。根据前面的分析,数值更大的 xx 绝不可能是反素数。

由此得到核心推论:反素数的质因子必由前若干个连续素数构成,且质因子指数单调不增:

c1⩾c2⩾c3⩾⋯⩾ck⩾0c_1 \geqslant c_2 \geqslant c_3 \geqslant \dots \geqslant c_k \geqslant 0

3. 搜索范围与剪枝

  1. 素数个数上界: 前 10 个素数的乘积为:
    2×3×5×7×11×13×17×19×23×29=6,469,693,230>2×1092 \times 3 \times 5 \times 7 \times 11 \times 13 \times 17 \times 19 \times 23 \times 29 = 6{,}469{,}693{,}230 > 2 \times 10^9
    因此在 n⩽2×109n \leqslant 2 \times 10^9 的范围内,任何候选数最多包含前 10 个素数。
  2. 最大指数上界: 因为 231>2×1092^{31} > 2 \times 10^9,所以最小素数 22 的指数 c1⩽30c_1 \leqslant 30。
  3. DFS 搜索流程: 从最小素数 22 开始按序递归枚举每个素数的指数:
    • 当前素数 pidxp_{idx} 的指数 cc 必须满足 0⩽c⩽cidx−10 \leqslant c \leqslant c_{idx-1}(保证指数单调不增);
    • 累乘过程中如果超出 nn 则立即剪枝回溯;
    • 遍历完或无法继续乘时,比较当前累积约数个数与全局最优解:优先选择约数个数更大者;约数个数相同时选择数值更小者。

可视化解析

以 n=1000n=1000 为例,前几个连续素数为 [2,3,5,7,11][2, 3, 5, 7, 11]。搜索树只在非递增指数序列上展开,候选形态如下表所示:

质因子分解 对应数值 xx 指数序列 约数个数 d(x)d(x) 是否更优 / 说明
292^9 512512 [9][9] 1010 初始较优
25×322^5 \times 3^2 288288 [5,2][5, 2] 6×3=186 \times 3 = 18 约数更多
24×32×512^4 \times 3^2 \times 5^1 720720 [4,2,1][4, 2, 1] 5×3×2=305 \times 3 \times 2 = 30 约数更多
23×31×51×712^3 \times 3^1 \times 5^1 \times 7^1 840840 [3,1,1,1][3, 1, 1, 1] 4×2×2×2=324 \times 2 \times 2 \times 2 = 32 最优解(全局最多)
22×32×51×712^2 \times 3^2 \times 5^1 \times 7^1 12601260 [2,2,1,1][2, 2, 1, 1] 3×3×2×2=363 \times 3 \times 2 \times 2 = 36 >1000> 1000,剪枝超出范围

状态转移逻辑如下图所示:

graph TD
    Root["开始搜索: DFS(p_idx=0, max_exp=31, num=1, divs=1)"]
    Root --> Choose2["枚举 2 的指数 c1 (1..30)"]
    Choose2 --> Choose3["枚举 3 的指数 c2 (1..c1)"]
    Choose3 --> Choose5["枚举 5 的指数 c3 (1..c2)"]
    Choose5 --> Dots["... 依序枚举后继质数"]
    Dots --> Check["更新最优解: divs > best_divs 或 (divs == best_divs 且 num < best_num)"]

代码

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:45
# update_at: 2026-09-30 22:45

import sys

# 前 10 个质数连乘已经超过 2*10^9 (2*3*5*...*29 = 6469693230 > 2*10^9)
PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]


def search(p_idx: int, max_exp: int, num: int, divs: int, bound: int, best: list[int]) -> None:
    """递归搜索质因子指数不增的候选数,维护约数最多且数值最小的解。

    best 结构:[best_num, max_divs]
    """
    better_divs = divs > best[1]
    same_divs_smaller = divs == best[1] and num < best[0]
    if better_divs or same_divs_smaller:
        best[0], best[1] = num, divs

    if p_idx == len(PRIMES):
        return

    p = PRIMES[p_idx]
    cur = num
    for exp in range(1, max_exp + 1):
        cur *= p
        if cur > bound:
            break
        search(p_idx + 1, exp, cur, divs * (exp + 1), bound, best)


def max_antiprime(n: int) -> int:
    """计算不大于 n 的最大反素数。"""
    best = [1, 1]  # [best_number, max_divisors]
    search(0, 31, 1, 1, n, best)
    return best[0]


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)
    print(max_antiprime(n))


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:10
 */

#include <cstdio>

typedef long long ll;

const int PNUM = 10; // n <= 2*10^9,前 10 个质数连乘 6469693230 已超界,最多用到第 10 个质数

// 前若干个质数:反素数的质因子必由连续前缀质数构成
ll pri[PNUM + 1] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29};

ll n;       // 题目给定的上界
ll best_num = 1;  // 当前最优反素数(约数最多,同约数个数时数值最小)
ll best_divs = 1; // 当前最优反素数的约数个数

// 按序枚举第 idx 个质数的指数 exp:1..max_exp(指数单调不增的剪枝)
// num:当前累积的数;divs:当前约数个数(各 (指数+1) 的乘积)
void dfs(ll idx, ll max_exp, ll num, ll divs) {
    // 每个节点都是一个候选数:约数更多者优先,约数相同取数值更小者
    if (divs > best_divs || (divs == best_divs && num < best_num)) {
        best_num = num;
        best_divs = divs;
    }

    if (idx > PNUM) return; // 质数用尽

    ll cur = num;
    // 这一层在选择第 idx 个质数的指数,指数不能超过上一层的 max_exp
    for (ll e = 1; e <= max_exp; ++e) {
        cur *= pri[idx];
        if (cur > n) break; // 超过 n,再乘只会更大,剪枝回溯
        dfs(idx + 1, e, cur, divs * (e + 1));
    }
}

int main() {
    scanf("%lld", &n);
    // 初始约数个数上界 31:2^31 > 2*10^9,质数 2 的指数最多 30
    dfs(1, 31, 1, 1);
    printf("%lld\n", best_num);
    return 0;
}

复杂度

  • 时间复杂度:满足 ∏i=1kpici⩽2×109\prod_{i=1}^k p_i^{c_i} \leqslant 2 \times 10^9 且 c1⩾c2⩾…c_1 \geqslant c_2 \geqslant \dots 的搜索状态数极其有限(合法状态总数不足几千个)。DFS 实际访问节点极少,运行耗时低于 0.03 s0.03\text{ s}。
  • 空间复杂度:递归深度不超过质数个数(最多 10 层),空间复杂度为 O(1)O(1)。

总结

反素数的核心在于将题目要求的“小于它的数约数个数均严格小于它”转化为“在 [1,n][1, n] 中求约数最多、数值最小的正整数”。再通过因数个数公式中底数与指数的对称性,利用反证法推导得出“质因子底数必为连续前缀素数且指数单调不增”。利用这一强剪枝条件,庞大的枚举空间被压缩到极小的规模,直接 DFS 即可在极短时间内得出正解。