素数 素数与合数的定义,见 数论基础 。
素数计数函数:小于或等于 𝑥 x 𝜋 ( 𝑥 ) π ( x ) 𝑥 x 𝜋 ( 𝑥 )   ∼ 𝑥 l n  ( 𝑥 ) π ( x ) ∼ x ln  ( x ) 
素性测试 素性测试 (Primality test)可以用于判定所给自然数是否为素数。
素性测试有两种:
确定性测试:绝对确定一个数是否为素数。常见例子包括试除法、Lucas–Lehmer 测试和椭圆曲线素性证明。 概率性测试:通常比确定性测试快很多,但有可能(尽管概率很小)错误地将 合数  识别为质数(尽管反之则不会)。因此,通过概率素性测试的数字被称为 可能素数 ,直到它们的素数可以被确定性地证明。而通过测试但实际上是合数的数字则被称为 伪素数 。有许多特定类型的伪素数,最常见的是费马伪素数,它们是满足费马小定理的合数。概率性测试的常见例子包括 Miller–Rabin 测试。 试除法 暴力做法自然可以枚举从小到大的每个数看是否能整除。
参考实现  这样做是十分稳妥了,但是真的有必要每个数都去判断吗?
很容易发现这样一个事实:如果 𝑥 x 𝑎 a 𝑎 𝑥 a x 𝑎 a 
这个结论告诉我们,对于每一对 ( 𝑥 , 𝑎 𝑥 ) ( x , a x ) [ 1 , √ 𝑎 ] [ 1 , a ] 
由于 1 1 
参考实现  Fermat 素性测试 Fermat 素性检验  是最简单的概率性素性检验。
我们可以根据 费马小定理  得出一种检验素数的思路:
基本思想是不断地选取在 [ 2 , 𝑛   − 1 ] [ 2 , n − 1 ] 𝑎 a 𝑎 𝑛 − 1   ≡ 1 ( m o d 𝑛 ) a n − 1 ≡ 1 ( mod n ) 
参考实现  C++ Python 
bool   fermat ( int   n )   { 
   if   ( n   <   3 )   return   n   ==   2 ; 
   // test_time 为测试次数,建议设为不小于 8 
   // 的整数以保证正确率,但也不宜过大,否则会影响效率 
   for   ( int   i   =   1 ;   i   <=   test_time ;   ++ i )   { 
     int   a   =   rand ()   %   ( n   -   2 )   +   2 ; 
     if   ( quickPow ( a ,   n   -   1 ,   n )   !=   1 )   return   false ; 
   } 
   return   true ; 
} 
def   fermat ( n ): 
    if  n  <  3 : 
        return  n  ==  2 
    # test_time 为测试次数,建议设为不小于 8 
    # 的整数以保证正确率,但也不宜过大,否则会影响效率 
    for  i  in  range ( 1 ,  test_time  +  1 ): 
        a  =  random . randint ( 0 ,  32767 )  %  ( n  -  2 )  +  2 
        if  quickPow ( a ,  n  -  1 ,  n )  !=  1 : 
            return  False 
    return  True 
如果 𝑎 𝑛 − 1   ≡ 1 ( m o d 𝑛 ) a n − 1 ≡ 1 ( mod n ) 𝑛 n 𝑛 n 𝑎 a Fermat 伪素数 。我们在实践中观察到,如果 𝑎 𝑛 − 1   ≡ 1 ( m o d 𝑛 ) a n − 1 ≡ 1 ( mod n ) 𝑛 n 𝑛   = 3 4 1 n = 341 𝑎   = 2 a = 2 2 3 4 0   ≡ 1 ( m o d 3 4 1 ) 2 340 ≡ 1 ( mod 341 ) 3 4 1   = 1 1   ⋅ 3 1 341 = 11 ⋅ 31 𝑎 a 
既然对于单个基底,Fermat 素性测试无法保证正确性,一个自然的想法就是多检查几组基底。但是,即使检查了所有可能的与 𝑛 n 𝑎 a 𝑛 n 𝑎   ⟂ 𝑛 a ⟂ n 𝑎 𝑛 − 1   ≡ 1 ( m o d 𝑛 ) a n − 1 ≡ 1 ( mod n ) 𝑛 n Carmichael 数 。它也有无穷多个。这迫使我们寻找更为严格的素性测试。
Miller–Rabin 素性测试 Miller–Rabin 素性测试 (Miller–Rabin primality test)是更好的素数判定方法。它是由 Miller 和 Rabin 二人根据 Fermat 素性测试优化得到的。和其它概率性素数测试一样,它也只能检测出伪素数。要确保是素数,需要用慢得多的确定性算法。然而,实际上没有已知的数字通过了 Miller–Rabin 测试等高级概率性测试但实际上却是合数,因此我们可以放心使用。
在不考虑乘法的复杂度时,对数 𝑛 n 𝑘 k 𝑂 ( 𝑘 l o g  𝑛 ) O ( k log  n ) 𝑂 ( 𝑘 l o g 3  𝑛 ) O ( k log 3  n ) 𝑂 ( 𝑘 l o g 2  𝑛 l o g  l o g  𝑛 l o g  l o g  l o g  𝑛 ) O ( k log 2  n log  log  n log  log  log  n ) 
为了解决 Carmichael 数带来的挑战,Miller–Rabin 素性测试进一步考虑了素数的如下性质:
二次探测定理  如果 𝑝 p 𝑥 2   ≡ 1 ( m o d 𝑝 ) x 2 ≡ 1 ( mod p ) 𝑥   ≡ 1 ( m o d 𝑝 ) x ≡ 1 ( mod p ) 𝑥   ≡ 𝑝   − 1 ( m o d 𝑝 ) x ≡ p − 1 ( mod p ) 
证明  容易验证,𝑝 p 𝑥   ≡ 1 ( m o d 𝑝 ) x ≡ 1 ( mod p ) 𝑥   ≡ 𝑝   − 1 ( m o d 𝑝 ) x ≡ p − 1 ( mod p ) Lagrange 定理  可知,这就是该方程的所有解。
将费马小定理和二次探测定理结合起来使用,就得到 Miller–Rabin 素性测试:
将 𝑎 𝑛 − 1   ≡ 1 ( m o d 𝑛 ) a n − 1 ≡ 1 ( mod n ) 𝑛   − 1 n − 1 𝑛   − 1   = 𝑢   × 2 𝑡 n − 1 = u × 2 t  在每轮测试中对随机出来的 𝑎 a 𝑣   = 𝑎 𝑢 m o d 𝑛 v = a u mod n 𝑡 t  在整个过程中,如果发现 1 1 ± 1 ± 1  否则,再使用 Fermat 素性测试判断。 还有一些实现上的小细节:
对于一轮测试,如果某一时刻 𝑎 𝑢 × 2 𝑠   ≡ 𝑛   − 1 ( m o d 𝑛 ) a u × 2 s ≡ n − 1 ( mod n ) 1 1  如果找出了一个非平凡平方根 𝑎 𝑢 × 2 𝑠   ≢ 𝑛   − 1 ( m o d 𝑛 ) a u × 2 s ≢ n − 1 ( mod n ) 1 1 false,也可以放到 𝑡 t false。 这样得到了较正确的 Miller Rabin:(来自 fjzzq2002)
参考实现  C++ Python 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 bool   millerRabin ( int   n )   { 
   if   ( n   <   3   ||   n   %   2   ==   0 )   return   n   ==   2 ; 
   if   ( n   %   3   ==   0 )   return   n   ==   3 ; 
   int   u   =   n   -   1 ,   t   =   0 ; 
   while   ( u   %   2   ==   0 )   u   /=   2 ,   ++ t ; 
   // test_time 为测试次数,建议设为不小于 8 
   // 的整数以保证正确率,但也不宜过大,否则会影响效率 
   for   ( int   i   =   0 ;   i   <   test_time ;   ++ i )   { 
     // 0, 1, n-1 可以直接通过测试, a 取值范围 [2, n-2] 
     int   a   =   rand ()   %   ( n   -   3 )   +   2 ,   v   =   quickPow ( a ,   u ,   n ); 
     if   ( v   ==   1 )   continue ; 
     int   s ; 
     for   ( s   =   0 ;   s   <   t ;   ++ s )   { 
       if   ( v   ==   n   -   1 )   break ;    // 得到平凡平方根 n-1,通过此轮测试 
       v   =   ( long   long ) v   *   v   %   n ; 
     } 
     // 如果找到了非平凡平方根,则会由于无法提前 break; 而运行到 s == t 
     // 如果 Fermat 素性测试无法通过,则一直运行到 s == t 前 v 都不会等于 -1 
     if   ( s   ==   t )   return   0 ; 
   } 
   return   1 ; 
} 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 def   millerRabin ( n ): 
    if  n  <  3  or  n  %  2  ==  0 : 
        return  n  ==  2 
    if  n  %  3  ==  0 : 
        return  n  ==  3 
    u ,  t  =  n  -  1 ,  0 
    while  u  %  2  ==  0 : 
        u  =  u  //  2 
        t  =  t  +  1 
    # test_time 为测试次数,建议设为不小于 8 
    # 的整数以保证正确率,但也不宜过大,否则会影响效率 
    for  i  in  range ( test_time ): 
        # 0, 1, n-1 可以直接通过测试, a 取值范围 [2, n-2] 
        a  =  random . randint ( 2 ,  n  -  2 ) 
        v  =  pow ( a ,  u ,  n ) 
        if  v  ==  1 : 
            continue 
        s  =  0 
        while  s  <  t : 
            if  v  ==  n  -  1 : 
                break 
            v  =  v  *  v  %  n 
            s  =  s  +  1 
        # 如果找到了非平凡平方根,则会由于无法提前 break; 而运行到 s == t 
        # 如果 Fermat 素性测试无法通过,则一直运行到 s == t 前 v 都不会等于 -1 
        if  s  ==  t : 
            return  False 
    return  True 
可以证明𝑛   > 9 n > 9 𝑎 a 𝑘 k 1 / 4 𝑘 1 / 4 k 
证明  设 𝑛   − 1   = 𝑢 2 𝑡 n − 1 = u 2 t 𝑢 u 𝑡 t 𝑛 n 𝑎 a 
 𝑎 𝑢 ≡ 1 ( m o d 𝑛 ) ,   o r   𝑎 𝑢 2 𝑖 ≡ − 1 ( m o d 𝑛 )   f o r   s o m e   0 ≤ 𝑖 < 𝑡 . a u ≡ 1 ( mod n ) ,  or  a u 2 i ≡ − 1 ( mod n )  for some  0 ≤ i < t . 记这样的 𝑎 a 𝑆 S 
 | 𝑆 | ≤ 1 4 𝜑 ( 𝑛 ) . | S | ≤ 1 4 φ ( n ) . 其中,𝜑 ( 𝑛 ) φ ( n ) 欧拉函数 。证明分为三步。
 第一步 :设 ℓ ℓ 2 ℓ   ∣ 𝑝   − 1 2 ℓ ∣ p − 1 𝑛 n 𝑝 p 
 𝑆 ⊆ 𝑆 ′ = { 𝑎 m o d 𝑛 : 𝑎 𝑢 2 ℓ − 1 ≡ ± 1 ( m o d 𝑛 ) } . S ⊆ S ′ = { a mod n : a u 2 ℓ − 1 ≡ ± 1 ( mod n ) } . 集合 𝑆 S 𝑎 a 𝑎 𝑢   ≡ 1 ( m o d 𝑛 ) a u ≡ 1 ( mod n ) 𝑎 𝑢 2 ℓ − 1   ≡ 1 ( m o d 𝑛 ) a u 2 ℓ − 1 ≡ 1 ( mod n ) 𝑎   ∈ 𝑆 ′ a ∈ S ′ 0   ≤ 𝑖   < 𝑡 0 ≤ i < t 𝑎 𝑢 2 𝑖   ≡   − 1 ( m o d 𝑛 ) a u 2 i ≡ − 1 ( mod n ) 𝑝   ∣ 𝑛 p ∣ n 𝑎 𝑢 2 𝑖   ≡   − 1 ( m o d 𝑝 ) a u 2 i ≡ − 1 ( mod p ) 𝛿 𝑝 ( 𝑎 ) δ p ( a ) 𝑎 a 𝑝 p 阶 ,那么,显然有 𝛿 𝑝 ( 𝑎 )   ∣ 𝑢 2 𝑖 + 1 δ p ( a ) ∣ u 2 i + 1 𝛿 𝑝 ( 𝑎 )   ∤ 𝑢 2 𝑖 δ p ( a ) ∤ u 2 i 𝛿 𝑝 ( 𝑎 ) δ p ( a ) 2 2 𝑖   + 1 i + 1 2 𝑖 + 1   ∣ 𝛿 𝑝 ( 𝑎 ) 2 i + 1 ∣ δ p ( a ) 𝛿 𝑝 ( 𝑎 )   ∣ 𝑝   − 1 δ p ( a ) ∣ p − 1 2 𝑖 + 1   ∣ 𝑝   − 1 2 i + 1 ∣ p − 1 𝑛 n 𝑝 p 𝑖   + 1   ≤ ℓ i + 1 ≤ ℓ 𝑎 𝑢 2 ℓ − 1   = ( 𝑎 𝑢 2 𝑖 ) 2 ℓ − 1 − 𝑖   ≡   ± 1 ( m o d 𝑛 ) a u 2 ℓ − 1 = ( a u 2 i ) 2 ℓ − 1 − i ≡ ± 1 ( mod n ) 𝑎   ∈ 𝑆 ′ a ∈ S ′ 𝑆   ⊆ 𝑆 ′ S ⊆ S ′ 
 第二步 :计算 | 𝑆 ′ | | S ′ | 
 假设 𝑛 n 𝑛   = 𝑝 𝑒 1 1 𝑝 𝑒 2 2 ⋯ 𝑝 𝑒 𝑘 𝑘 n = p 1 e 1 p 2 e 2 ⋯ p k e k 中国剩余定理  可知,条件 𝑎 𝑢 2 ℓ − 1   ≡ 1 ( m o d 𝑛 ) a u 2 ℓ − 1 ≡ 1 ( mod n ) 𝑎 𝑢 2 ℓ − 1   ≡ 1 ( m o d 𝑝 𝑒 𝑖 𝑖 ) a u 2 ℓ − 1 ≡ 1 ( mod p i e i ) 𝑝 𝑒 𝑖 𝑖 p i e i 𝑝 𝑒 𝑖 𝑖 p i e i 原根  总是存在的,所以,同余方程 𝑎 𝑢 2 ℓ − 1   ≡ 1 ( m o d 𝑝 𝑒 𝑖 𝑖 ) a u 2 ℓ − 1 ≡ 1 ( mod p i e i ) 解的数量  为
 g c d ( 𝑢 2 ℓ − 1 , 𝑝 𝑒 𝑖 − 1 𝑖 ( 𝑝 𝑖 − 1 ) ) = g c d ( 𝑢 2 ℓ − 1 , 𝑝 𝑖 − 1 ) = 2 ℓ − 1 g c d ( 𝑢 , 𝑝 𝑖 − 1 ) . gcd ( u 2 ℓ − 1 , p i e i − 1 ( p i − 1 ) ) = gcd ( u 2 ℓ − 1 , p i − 1 ) = 2 ℓ − 1 gcd ( u , p i − 1 ) . 第一个等号成立,是因为 𝑢 u 𝑛   − 1 n − 1 𝑝 𝑖 p i ℓ ℓ 𝑎 𝑢 2 ℓ − 1   ≡ 1 ( m o d 𝑛 ) a u 2 ℓ − 1 ≡ 1 ( mod n ) 
 ∏ 𝑝 ∣ 𝑛 2 ℓ − 1 g c d ( 𝑢 , 𝑝 − 1 ) . ∏ p ∣ n 2 ℓ − 1 gcd ( u , p − 1 ) . 同理,条件 𝑎 𝑢 2 ℓ − 1   ≡   − 1 ( m o d 𝑛 ) a u 2 ℓ − 1 ≡ − 1 ( mod n ) 𝑎 𝑢 2 ℓ − 1   ≡   − 1 ( m o d 𝑝 𝑒 𝑖 𝑖 ) a u 2 ℓ − 1 ≡ − 1 ( mod p i e i ) 𝑝 𝑒 𝑖 𝑖 p i e i 𝑝 𝑒 𝑖 𝑖 p i e i 𝑎 𝑢 2 ℓ − 1   ≡   − 1 ( m o d 𝑝 𝑒 𝑖 𝑖 ) a u 2 ℓ − 1 ≡ − 1 ( mod p i e i ) 𝑎 𝑢 2 ℓ − 1   ≢ 1 ( m o d 𝑝 𝑒 𝑖 𝑖 ) a u 2 ℓ − 1 ≢ 1 ( mod p i e i ) 𝑎 𝑢 2 ℓ   ≡ 1 ( m o d 𝑝 𝑒 𝑖 𝑖 ) a u 2 ℓ ≡ 1 ( mod p i e i ) 𝑎 𝑢 2 ℓ   ≡ 1 ( m o d 𝑝 𝑒 𝑖 𝑖 ) a u 2 ℓ ≡ 1 ( mod p i e i ) 2 ℓ g c d ( 𝑢 , 𝑝 𝑖   − 1 ) 2 ℓ gcd ( u , p i − 1 ) 𝑎 𝑢 2 ℓ − 1   ≡   − 1 ( m o d 𝑝 𝑒 𝑖 𝑖 ) a u 2 ℓ − 1 ≡ − 1 ( mod p i e i ) 
 2 ℓ g c d ( 𝑢 , 𝑝 𝑖 − 1 ) − 2 ℓ − 1 g c d ( 𝑢 , 𝑝 𝑖 − 1 ) = 2 ℓ − 1 g c d ( 𝑢 , 𝑝 𝑖 − 1 ) . 2 ℓ gcd ( u , p i − 1 ) − 2 ℓ − 1 gcd ( u , p i − 1 ) = 2 ℓ − 1 gcd ( u , p i − 1 ) . 再次应用中国剩余定理,就得到同余方程 𝑎 𝑢 2 ℓ − 1   ≡   − 1 ( m o d 𝑛 ) a u 2 ℓ − 1 ≡ − 1 ( mod n ) 
 ∏ 𝑝 ∣ 𝑛 2 ℓ − 1 g c d ( 𝑢 , 𝑝 − 1 ) . ∏ p ∣ n 2 ℓ − 1 gcd ( u , p − 1 ) . 因此,综合两种情形,有
 | 𝑆 ′ | = 2 ∏ 𝑝 ∣ 𝑛 2 ℓ − 1 g c d ( 𝑢 , 𝑝 − 1 ) . | S ′ | = 2 ∏ p ∣ n 2 ℓ − 1 gcd ( u , p − 1 ) . 第三步 :证明 | 𝑆 ′ |   ≤ 𝜑 ( 𝑛 ) / 4 | S ′ | ≤ φ ( n ) / 4 
 结合欧拉函数的表达式 𝜑 ( 𝑛 )   = ∏ 𝑖 𝑝 𝑒 𝑖 − 1 𝑖 ( 𝑝 𝑖   − 1 ) φ ( n ) = ∏ i p i e i − 1 ( p i − 1 ) 
 𝜑 ( 𝑛 ) | 𝑆 ′ | = 1 2 ∏ 𝑖 𝑝 𝑒 𝑖 − 1 𝑖 𝑝 𝑖 − 1 2 ℓ − 1 g c d ( 𝑢 , 𝑝 𝑖 − 1 ) . φ ( n ) | S ′ | = 1 2 ∏ i p i e i − 1 p i − 1 2 ℓ − 1 gcd ( u , p i − 1 ) . 对于每一个 𝑖 i 𝑝 𝑒 𝑖 − 1 𝑖 𝑝 𝑖 − 1 2 ℓ − 1 g c d ( 𝑢 , 𝑝 𝑖 − 1 ) p i e i − 1 p i − 1 2 ℓ − 1 gcd ( u , p i − 1 ) 𝜑 ( 𝑛 ) / | 𝑆 ′ | φ ( n ) / | S ′ | | 𝑆 ′ |   ≤ 𝜑 ( 𝑛 ) / 4 | S ′ | ≤ φ ( n ) / 4 𝜑 ( 𝑛 ) / | 𝑆 ′ |   = 1 , 2 , 3 φ ( n ) / | S ′ | = 1 , 2 , 3 
 ∏ 𝑖 𝑝 𝑒 𝑖 − 1 𝑖 𝑝 𝑖 − 1 2 ℓ − 1 g c d ( 𝑢 , 𝑝 𝑖 − 1 ) = 2 , 4 , 6 . ∏ i p i e i − 1 p i − 1 2 ℓ − 1 gcd ( u , p i − 1 ) = 2 , 4 , 6. 由于连乘式中的每个因子都是偶数,所以,这个连乘式要么只有一个因子且这个因子就等于 2 , 4 , 6 2 , 4 , 6 2 2 
 首先考虑有两个因子的情形。此时,两个因子都没有奇素因子,所以,𝑝 𝑒 𝑖 − 1 𝑖   = 1 p i e i − 1 = 1 𝑛 n 𝑛   = 𝑝 1 𝑝 2 n = p 1 p 2 𝑝 1   < 𝑝 2 p 1 < p 2 2 2 𝑝 𝑖   − 1   = 2 ℓ g c d ( 𝑢 , 𝑝 𝑖   − 1 ) p i − 1 = 2 ℓ gcd ( u , p i − 1 ) 𝑝 𝑖   = 1   + 2 ℓ 𝑚 𝑖 p i = 1 + 2 ℓ m i 𝑚 𝑖 m i 𝑚 𝑖   ∣ 𝑢 m i ∣ u 𝑝 1 𝑝 2   = 𝑛   = 1   + 𝑢 2 𝑡 p 1 p 2 = n = 1 + u 2 t 𝑚 1 m 1 𝑝 1 𝑝 2   ≡ 1 ( m o d 𝑚 1 ) p 1 p 2 ≡ 1 ( mod m 1 ) 𝑝 2   ≡ 1 ( m o d 𝑚 1 ) p 2 ≡ 1 ( mod m 1 ) 𝑚 1   ∣ 𝑚 2 m 1 ∣ m 2 𝑚 1   = 𝑚 2 m 1 = m 2 𝑝 1   = 𝑝 2 p 1 = p 2 𝑝 1   < 𝑝 2 p 1 < p 2 
 最后,考虑只有一个因子的情形,亦即合数 𝑛   = 𝑝 𝑒 n = p e 𝑒   > 1 e > 1 𝑝 𝑒 − 1   ∣ 2 , 4 , 6 p e − 1 ∣ 2 , 4 , 6 𝑝   = 3 , 𝑒   = 2 p = 3 , e = 2 𝑛   = 9 n = 9 
 综合所有情形可知,| 𝑆 ′ |   ≤ 𝜑 ( 𝑛 ) / 4 | S ′ | ≤ φ ( n ) / 4 
 结合上述三个步骤可知,| 𝑆 |   ≤ | 𝑆 ′ |   ≤ 𝜑 ( 𝑛 ) / 4 | S | ≤ | S ′ | ≤ φ ( n ) / 4 𝑛   > 9 n > 9 
另外,假设 广义 Riemann 猜想 (generalized Riemann hypothesis, GRH)成立,则对数 𝑛 n [ 2 , m i n { 𝑛   − 2 , ⌊ 2 l n 2  𝑛 ⌋ } ] [ 2 , min { n − 2 , ⌊ 2 ln 2  n ⌋ } ] 确定  数 𝑛 n 
而在 OI 范围内,通常都是对 [ 1 , 2 6 4 ) [ 1 , 2 64 ) [ 1 , 2 3 2 ) [ 1 , 2 32 ) { 2 , 7 , 6 1 } { 2 , 7 , 61 } [ 1 , 2 6 4 ) [ 1 , 2 64 ) { 2 , 3 2 5 , 9 3 7 5 , 2 8 1 7 8 , 4 5 0 7 7 5 , 9 7 8 0 5 0 4 , 1 7 9 5 2 6 5 0 2 2 } { 2 , 325 , 9375 , 28178 , 450775 , 9780504 , 1795265022 } 
也可以选取 { 2 , 3 , 5 , 7 , 1 1 , 1 3 , 1 7 , 1 9 , 2 3 , 2 9 , 3 1 , 3 7 } { 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 , 31 , 37 } 1 2 12 [ 1 , 2 6 4 ) [ 1 , 2 64 ) 
注意如果要使用上面的数列中的数 𝑎 a 𝑛 n 
所有的数都要取一遍,不能只选小于 𝑛 n  把 𝑎 a 𝑎 m o d 𝑛 a mod n  如果 𝑎   ≡ 0 ( m o d 𝑛 ) a ≡ 0 ( mod n ) 𝑎   ≡   ± 1 ( m o d 𝑛 ) a ≡ ± 1 ( mod n )  反素数 顾名思义,素数就是因子只有两个的数,那么反素数,就是因子最多的数(并且因子个数相同的时候值最小),所以反素数是相对于一个集合来说的。
一种符合直觉的反素数定义是:在一个正整数集合中,因子最多并且值最小的数,就是反素数。
反素数  对于某个正整数 𝑛 n 𝑛 n 𝑛 n 反素数 (anti-prime, a.k.a., highly compositive numbers)。
注意  注意区分 emirp ,它表示的是逐位反转后是不同素数的素数(如 149 和 941 均为 emirp,101 不是 emirp)。
过程 那么,如何来求解反素数呢?
首先,既然要求因子数,首先要做的就是素因子分解。把 𝑛 n 𝑛   = 𝑝 𝑘 1 1 𝑝 𝑘 2 2 ⋯ 𝑝 𝑘 𝑛 𝑛 n = p 1 k 1 p 2 k 2 ⋯ p n k n 𝑝 p 𝑘 k ( 𝑘 1   + 1 )   × ( 𝑘 2   + 1 )   × ( 𝑘 3   + 1 ) ⋯   × ( 𝑘 𝑛   + 1 ) ( k 1 + 1 ) × ( k 2 + 1 ) × ( k 3 + 1 ) ⋯ × ( k n + 1 ) 
但是显然质因子分解的复杂度是很高的,并且前一个数的结果不能被后面利用。所以要换个方法。
我们来观察一下反素数的特点。
反素数肯定是从 2 2 
数值小的素数的幂次大于等于数值大的素数,即 𝑛   = 𝑝 𝑘 1 1 𝑝 𝑘 2 2 ⋯ 𝑝 𝑘 𝑛 𝑛 n = p 1 k 1 p 2 k 2 ⋯ p n k n 𝑘 1   ≥ 𝑘 2   ≥ 𝑘 3   ≥ ⋯   ≥ 𝑘 𝑛 k 1 ≥ k 2 ≥ k 3 ≥ ⋯ ≥ k n 
解释:
如果不是从 2 2 𝑛 n 2 2 𝑛 n 
如果数值小的素数的幂次小于数值大的素数的幂,那么如果把这两个素数交换位置(幂次不变),那么所得的 𝑛 n 𝑛 n 
另外还有两个问题,
对于给定的 𝑛 n 
 最极端的情况大不了就是 𝑛   = 𝑝 1 𝑝 2 ⋯ 𝑝 𝑛 n = p 1 p 2 ⋯ p n 𝑛 n 
我们要枚举到多少次幂呢?
 我们考虑一个极端情况,当我们最小的素数的某个幂次已经比所给的 𝑛 n unsigned long long 的最大值是 2 6 4   − 1 2 64 − 1 2 6 4   − 1 2 64 − 1 
细节有了,那么我们具体如何具体实现呢?
我们可以把当前走到每一个素数前面的时候列举成一棵树的根节点,然后一层层的去找。找到什么时候停止呢?
当前走到的数字已经大于我们想要的数字了;
当前枚举的因子已经用不到了;
当前因子大于我们想要的因子了;
当前因子正好是我们想要的因子(此时判断是否需要更新最小 𝑎 𝑛 𝑠 a n s 
然后 dfs 里面不断一层一层枚举次数继续往下迭代可以。
例题 Codeforces 27E. A number with a given number of divisors 求具有给定除数个数的最小自然数。答案保证不超过 1 0 1 8 10 18 
解题思路  对于这种题,我们只要以因子数为 dfs 的返回条件基准,不断更新找到的最小值就可以了。
参考代码   1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 #include   <iostream> 
unsigned   long   long   p [ 16 ]   =   { 
     2 ,    3 ,    5 ,    7 ,    11 ,   13 ,   17 ,   19 , 
     23 ,   29 ,   31 ,   37 ,   41 ,   43 ,   47 ,   53 };    // 根据数据范围可以确定使用的素数最大为53 
unsigned   long   long   ans ; 
unsigned   long   long   n ; 
// depth: 当前在枚举第几个素数 
// temp: 当前因子数量为 num的时候的数值 
// num: 当前因子数 
// up:上一个素数的幂,这次应该小于等于这个幂次嘛 
void   dfs ( unsigned   long   long   depth ,   unsigned   long   long   temp , 
          unsigned   long   long   num ,   unsigned   long   long   up )   { 
   if   ( num   >   n   ||   depth   >=   16 )   return ;    // 边界条件 
   if   ( num   ==   n   &&   ans   >   temp )   {          // 取最小的ans 
     ans   =   temp ; 
     return ; 
   } 
   for   ( int   i   =   1 ;   i   <=   up ;   i ++ )   { 
     if   ( temp   *   p [ depth ]   >   ans ) 
       break ;    // 剪枝:如果加一个这个乘数的结果比ans要大,则必不是最佳方案 
     dfs ( depth   +   1 ,   temp   =   temp   *   p [ depth ],   num   *   ( i   +   1 ), 
         i );    // 取一个该乘数,进行对下一个乘数的搜索 
   } 
} 
using   std :: cin ; 
using   std :: cout ; 
int   main ()   { 
   cin . tie ( nullptr ) -> sync_with_stdio ( false ); 
   cin   >>   n ; 
   ans   =   ~ ( unsigned   long   long ) 0 ; 
   dfs ( 0 ,   1 ,   1 ,   64 ); 
   cout   <<   ans   <<   '\n' ; 
   return   0 ; 
} 
ZOJ 2562 More Divisors 求不超过 𝑛 n 
解题思路  思路同上,只不过要改改 dfs 的返回条件。注意这样的题目的数据范围,32 位整数可能溢出。
参考代码   1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 #include   <iostream> 
int   p [ 16 ]   =   { 2 ,   3 ,   5 ,   7 ,   11 ,   13 ,   17 ,   19 ,   23 ,   29 ,   31 ,   37 ,   41 ,   43 ,   47 ,   53 }; 
unsigned   long   long   n ; 
unsigned   long   long   ans , 
     ans_num ;    // ans 为 n 以内的最大反素数(会持续更新),ans_sum 为 
               // ans的因子数。 
// depth: 当前在枚举第几个素数 
// temp: 当前因子数量为 num的时候的数值 
// num: 当前因子数 
// up:上一个素数的幂,这次应该小于等于这个幂次嘛 
void   dfs ( int   depth ,   unsigned   long   long   temp ,   unsigned   long   long   num ,   int   up )   { 
   if   ( depth   >=   16   ||   temp   >   n )   return ; 
   if   ( num   >   ans_num )   {    // 更新答案 
     ans   =   temp ; 
     ans_num   =   num ; 
   } 
   if   ( num   ==   ans_num   &&   ans   >   temp )   ans   =   temp ;    // 更新答案 
   for   ( int   i   =   1 ;   i   <=   up ;   i ++ )   { 
     if   ( temp   *   p [ depth ]   >   n ) 
       break ;    // 剪枝:如果加一个这个乘数的结果比ans要大,则必不是最佳方案 
     dfs ( depth   +   1 ,   temp   *=   p [ depth ],   num   *   ( i   +   1 ), 
         i );    // 取一个该乘数,进行对下一个乘数的搜索 
   } 
   return ; 
} 
using   std :: cin ; 
using   std :: cout ; 
int   main ()   { 
   cin . tie ( nullptr ) -> sync_with_stdio ( false ); 
   while   ( cin   >>   n )   { 
     ans_num   =   0 ; 
     dfs ( 0 ,   1 ,   1 ,   60 ); 
     cout   <<   ans   <<   '\n' ; 
   } 
   return   0 ; 
} 
参考资料与注释 Rui-Juan Jing, Marc Moreno-Maza, Delaram Talaashrafi, "Complexity Estimates for Fourier-Motzkin Elimination ", Journal of Functional Programming 16:2 (2006) pp 197-217. 数论部分第一节:素数与素性测试 Miller–Rabin 与 Pollard–Rho 学习笔记 - Bill Yang's Blog Primality test - Wikipedia Fermat pseudoprime - Wikipedia 桃子的算法笔记——反素数详解(acm/OI) The Rabin-Miller Primality Test Highly composite number - Wikipedia 2025/8/30 15:23:07 ,更新历史 在 GitHub 上编辑此页! Ir1d , Tiphereth-A , c-forrest , Xeonacid , Enter-tainer , StudyingFather , iamtwz , ksyx , Marcythm , MegaOwIer , 383494 , Alpacabla , HeRaNO , abc1763613206 , alphagocc , Backl1ght , CCXXXI , drkelo , Early0v0 , Great-designer , greyqz , GuanghaoYe , H-J-Granger , HHH2309 , isdanni , kenlig , lazyasn , Menci , ouuan , r-value , shawlleyw , shopee-jin , shuzhouliu , Siger Young , TrisolarisHD , untitledunrevised , void-mian , Voileexperiments , weilycoder , xtlsoft , yusancky , YuzhenQin1 CC BY-SA 4.0  和 SATA