素数定义:
质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数(规定1既不是质数也不是合数)。
素数计数函数:小于或等于 x x x 的素数的个数,用 π ( x ) \pi(x) π(x) 表示。随着 x x x 的增大,有这样的近似结果: π ( x ) ∼ x ln ( x ) \pi(x) \sim \frac{x}{\ln(x)} π(x)∼ln(x)x
素数判定:
暴力做法
自然可以枚举从小到大的每个数看是否能整除
bool isPrime(a) {
if (a < 2) return 0;
for (int i = 2; i * i <= a; ++i)
if (a % i == 0) return 0;
return 1;
}
这样做是十分稳妥了,但是真的有必要每个数都去判断吗?
很容易发现这样一个事实:如果
x
x
x 是
a
a
a 的约数,那么
a
x
\frac{a}{x}
xa 也是
a
a
a 的约数。
这个结论告诉我们,对于每一对
(
x
,
a
x
)
(x, \frac{a}{x} )
(x,xa),只需要检验其中的一个就好了。为了方便起见,我们之考察每一对里面小的那个数。不难发现,所有这些较小数就是
[
1
,
a
]
[1, \sqrt{a}]
[1,a] 这个区间里的数。
由于
1
1
1 肯定是约数,所以不检验它。
Miller-Rabin 素性测试
二次探测定理
如果 p p p 是奇素数,则 x 2 ≡ 1 ( m o d p ) x^2 \equiv 1 \pmod p x2≡1(modp) 的解为 x ≡ 1 ( m o d p ) x \equiv 1 \pmod p x≡1(modp) 或者 x ≡ p − 1 ( m o d p ) x \equiv p - 1 \pmod p x≡p−1(modp)。
要证明该定理,只需将上面的方程移项,再使用平方差公式,得到 ( x + 1 ) ( x − 1 ) ≡ 0 m o d p (x+1)(x-1) \equiv 0 \bmod p (x+1)(x−1)≡0modp,即可得出上面的结论。
实现
根据卡迈克尔数的性质,可知其一定不是 p e p^e pe。
不妨将费马小定理和二次探测定理结合起来使用:
将 a n − 1 ≡ 1 ( m o d n ) a^{n-1} \equiv 1 \pmod n an−1≡1(modn) 中的指数 n − 1 n−1 n−1 分解为 n − 1 = u × 2 t n−1=u \times 2^t n−1=u×2t,在每轮测试中对随机出来的 a a a 先求出 a u ( m o d n ) a^{u} \pmod n au(modn),之后对这个值执行最多 t t t 次平方操作,若发现非平凡平方根时即可判断出其不是素数,否则通过此轮测试。
bool millerRabin(int n) {
if (n < 3 || n % 2 == 0) return n == 2;
int a = n - 1, b = 0;
while (a % 2 == 0) a /= 2, ++b;
// test_time 为测试次数,建议设为不小于 8
// 的整数以保证正确率,但也不宜过大,否则会影响效率
for (int i = 1, j; i <= test_time; ++i) {
int x = rand() % (n - 2) + 2, v = quickPow(x, a, n);
if (v == 1) continue;
for (j = 0; j < b; ++j) {
if (v == n - 1) break;
v = (long long)v * v % n;
}
if (j >= b) return 0;
}
return 1;
}
反素数定义:
如果某个正整数
n
n
n 满足如下条件,则称为是反素数:
任何小于
n
n
n 的正数的约数个数都小于
n
n
n 的约数个数
其实顾名思义,素数就是因子只有两个的数,那么反素数,就是因子最多的数(并且因子个数相同的时候值最小),所以反素数是相对于一个集合来说的。即在一个集合中,因素最多并且值最小的数,就是反素数。
那么,如何来求解反素数呢?
首先,既然要求因子数,我首先想到的就是素因子分解。把
n
n
n 分解成
n
=
p
1
k
1
p
2
k
2
⋯
p
n
k
n
n=p_{1}^{k_{1}}p_{2}^{k_{2}} \cdots p_{n}^{k_{n}}
n=p1k1p2k2⋯pnkn 的形式,其中
p
p
p 是素数,
k
k
k 为他的指数。这样的话总因子个数就是
(
k
1
+
1
)
×
(
k
2
+
1
)
×
(
k
3
+
1
)
⋯
×
(
k
n
+
1
)
(k_1+1) \times (k_2+1) \times (k_3+1) \cdots \times (k_n+1)
(k1+1)×(k2+1)×(k3+1)⋯×(kn+1)。
但是显然质因子分解的复杂度是很高的,并且前一个数的结果不能被后面利用。所以要换个方法。
我们来观察一下反素数的特点。
-
反素数肯定是从 2 2 2 开始的连续素数的幂次形式的乘积。
-
数值小的素数的幂次大于等于数值大的素数,即 n = p 1 k 1 p 2 k 2 ⋯ p n k n n=p_{1}^{k_{1}}p_{2}^{k_{2}} \cdots p_{n}^{k_{n}} n=p1k1p2k2⋯pnkn 中,有 k 1 ≥ k 2 ≥ k 3 ≥ ⋯ ≥ k n k_1 \geq k_2 \geq k_3 \geq \cdots \geq k_n k1≥k2≥k3≥⋯≥kn
解释:
- 如果不是从 2 2 2 开始的连续素数,那么如果幂次不变,把素数变成数值更小的素数,那么此时因子个数不变,但是 n n n 的数值变小了。交换到从 2 2 2 开始的连续素数的时候 n n n 值最小。
- 如果数值小的素数的幂次小于数值大的素数的幂,那么如果把这两个素数交换位置(幂次不变),那么所得的 n n n 因子数量不变,但是 n n n 的值变小。
另外还有两个问题,
- 对于给定的
n
n
n,要枚举到哪一个素数呢?
最极端的情况大不了就是 n = p 1 p 2 ⋯ p n n=p_{1}p_{2} \cdots p_{n} n=p1p2⋯pn,所以只要连续素数连乘到刚好小于等于 n n n 就可以的呢。再大了,连全都一次幂,都用不了,当然就是用不到的啦! - 我们要枚举到多少次幂呢?
我们考虑一个极端情况,当我们最小的素数的某个幂次已经比所给的 n n n(的最大值)大的话,那么展开成其他的形式,最大幂次一定小于这个幂次。unsigned long long 的最大值是 2 的 64 次方,所以我这边习惯展开成 2 的 64 次方。
细节有了,那么我们具体如何具体实现呢?
我们可以把当前走到每一个素数前面的时候列举成一棵树的根节点,然后一层层的去找。找到什么时候停止呢?
-
当前走到的数字已经大于我们想要的数字了
-
当前枚举的因子已经用不到了(和 1 1 1 重复了嘻嘻嘻)
-
当前因子大于我们想要的因子了
-
当前因子正好是我们想要的因子(此时判断是否需要更新最小 a n s ans ans)
然后 dfs 里面不断一层一层枚举次数继续往下迭代就好啦~~
常见题型
求因子数一定的最小数
题目链接:https://codeforces.com/problemset/problem/27/E
题意:求最小的有
n
n
n 个因数的数
s
s
s。
n
≤
1
0
3
n \leq 10^3
n≤103 ,保证
s
≤
1
0
18
s \leq 10^{18}
s≤1018
考虑质因数分解:
s
=
∏
i
=
1
k
p
i
a
i
p
i
s = \prod_{i=1}^k p_i^{a_i}\;\;\;\;\;\;\;p_i
s=∏i=1kpiaipi为质数
那么
s
s
s 的因数个数就会是
∏
i
=
1
k
(
a
i
+
1
)
\prod_{i=1}^k (a_i + 1)
∏i=1k(ai+1)
考虑最大的
p
i
p_i
pi会是几呢?
2
∗
3
∗
5
∗
7
∗
11
∗
13
∗
17
∗
19
∗
23
∗
29
∗
31
∗
37
∗
41
∗
43
∗
47
=
614889782588491410
2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23 * 29 * 31 * 37 * 41 * 43 * 47 = 614889782588491410
2∗3∗5∗7∗11∗13∗17∗19∗23∗29∗31∗37∗41∗43∗47=614889782588491410约为
6.1
×
1
0
17
6.1\times 10^{17}
6.1×1017,所以
p
i
p_i
pi最大为 53.
我们只要以因子数为 dfs 的返回条件基准,不断更新找到的最小值就可以了
上代码:
#include <stdio.h>
#define ULL unsigned long long
#define INF ~0ULL
ULL p[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
ULL ans;
ULL n;
// depth: 当前在枚举第几个素数。num: 当前因子数。
// temp: 当前因子数量为 num
// 的时候的数值。up:上一个素数的幂,这次应该小于等于这个幂次嘛
void dfs(ULL depth, ULL temp, ULL num, ULL up) {
if (num > n || depth >= 16) return;
if (num == n && ans > temp) {
ans = temp;
return;
}
for (int i = 1; i <= up; i++) {
if (temp / p[depth] > ans) break;
dfs(depth + 1, temp = temp * p[depth], num * (i + 1), i);
}
}
int main() {
while (scanf("%llu", &n) != EOF) {
ans = INF;
dfs(0, 1, 1, 64);
printf("%llu\n", ans);
}
return 0;
}
求 n 以内因子数最多的数
https://zoj.pintia.cn/problem-sets/91827364500/problems/91827366061
思路同上,只不过要改改 dfs 的返回条件。注意这样的题目的数据范围,我一开始用了 int,应该是溢出了,在循环里可能就出不来了就超时了。上代码,0ms 过。注释就没必要写了上面写的很清楚了。
#include <cstdio>
#include <iostream>
#define ULL unsigned long long
int p[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
ULL n;
ULL ans, ans_num; // ans 为 n 以内的最大反素数(会持续更新),ans_sum 为 ans
// 的因子数。
void dfs(int depth, ULL temp, ULL 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;
dfs(depth + 1, temp *= p[depth], num * (i + 1), i);
}
return;
}
int main() {
while (scanf("%llu", &n) != EOF) {
ans_num = 0;
dfs(0, 1, 1, 60);
printf("%llu\n", ans);
}
return 0;
}