【算法简介】
- 素数测试是一种判断一个数是否是质数的方式。
- 其单次测试的时间复杂度不会超过 ,期望为 ,几乎不需要额外的空间。
- 素数测试不是一个确定算法,其单次测试有不超过 的概率会将一个合数误判为一个素数。但当被测试数在某一个范围内时,我们可以通过选取适当的测试底数让 素数测试对于这个范围内的每一个数都能够正确地得出结果。
【算法流程】
一个质数具有许多合数不具有的性质,例如:
定理 :若 是质数,则对于任意 ,有 。
引理 :若 是质数,且 ,那么 和 中的一个成立。
其中定理 就是 费马小定理 ,引理 的证明的如下:
由于 ,有 。
即 是 的倍数,因此引理 得证。
那么,一个简单的想法就是我们对于被测试数 ,分别测试定理 和引理 是否成立,若发现不成立,则说明被测试数 一定是一个合数。
具体而言,我们首先要选取一个底数 ,计算 ,若 ,那么 一定是一个合数。
否则,即 ,若此时 为偶数,我们还可以计算 , 若 且 ,那么 一定是一个合数。
否则,若 ,并且此时 为偶数,我们还可以计算 , 若 且 ,那么 一定是一个合数。
如此重复,直到 被确定是一个合数,或者 ,或者 为奇数为止。可以发现,这个过程最多会进行 次,每一次我们需要计算一个乘幂,因此单次测试的最坏时间复杂度为 。
可以发现,一些合数同样能够通过以某些 为底的 素数测试,一种可行的选择是随机底数 ,进行 次 素数测试,算法的错误几率将小于 。
前面提到,当被测试数在某一个范围内时,我们可以通过选取适当的测试底数让 素数测试对于这个范围内的每一个数都能够正确地得出结果。下面是节选自 维基百科 的选取 的方式。
当 ,选取 即可确保算法得出正确结果。
当 ,选取 即可确保算法得出正确结果。
当 ,选取 即可确保算法得出正确结果。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int p[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
typedef long long ll;
typedef long double ld;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
ll times(ll a, ll b, ll P) {
ll tmp = (ld) a * b / P;
return ((a * b - tmp * P) % P + P) % P;
}
ll power(ll a, ll b, ll P) {
if (b == 0) return 1;
ll tmp = power(a, b / 2, P);
if (b % 2 == 0) return times(tmp, tmp, P);
else return times(a, times(tmp, tmp, P), P);
}
bool prime(ll n) {
for (int i = 0; i <= 8; i++) {
if (p[i] == n) return true;
else if (p[i] > n) return false;
ll tmp = power(p[i], n - 1, n), tnp = n - 1;
if (tmp != 1) return false;
while (tmp == 1 && tnp % 2 == 0) {
tnp /= 2;
tmp = power(p[i], tnp, n);
if (tmp != 1 && tmp != n - 1) return false;
}
}
return true;
}
int main() {
ll n;
while (scanf("%lld", &n) != EOF) {
if (n == 1) {
puts("N");
continue;
}
if (prime(n)) puts("Y");
else puts("N");
}
return 0;
}