【LOJ#143】质数判定
题目
题目链接:https://loj.ac/p/143
给定一个数 \(n\),判断是否是质数。
\(n\leq 10^{18}\)。
思路
Miller–Rabin 素数测试板子题。推荐博客。
质数有两个性质。一是众所周知的费马小定理:若 \(p\) 是质数,且 \(x\) 不是 \(p\) 的倍数,则
\[x^{p-1}\equiv 1\pmod p
\]
还有一个二次探测定理。如果 \(p\) 是质数且 \(x^2\equiv 1\pmod p\),那么 \(x\equiv1\pmod p\) 或 \(x\equiv p-1\pmod p\)。
证明直接把 \(1\) 移项平方差即可。
所以对于一个要测试的数 \(p\),我们取一个数 \(a\),先判断 \(a^{p-1}\bmod p\) 是否等于 \(1\)。如果不是那么显然 \(p\) 是合数。
接下来重复此操作:
- 如果 \(p-1\) 是奇数则直接退出循环。
- 计算 \(a^{\frac{p-1}{2}}\bmod p\),根据二次探测定理,如果答案不是 \(1\) 或 \(n\) 则 \(p\) 是合数。
- 如果答案是 \(1\) 则取 \(p'=\frac{p-1}{2}\) 继续探测。
随机选 \(a\) 的话,Miller–Rabin 有 \(\frac{1}{4}\) 的概率出错。所以如果测试 \(k\) 次,测试错误的概率就降至 \(\frac{1}{4^k}\)。
在数据范围不同时 \(a\) 的选取可以参考 wiki。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long double ld;
typedef long long ll;
const int prm[10]={0,2,3,5,7,11,13,17,19,23};
ll n;
ll fmul(ll x,ll y,ll MOD)
{
ll z=(ld)x*y/MOD;
return ((x*y-z*MOD)%MOD+MOD)%MOD;
}
ll fpow(ll x,ll k,ll MOD)
{
ll ans=1;
for (;k;k>>=1,x=fmul(x,x,MOD))
if (k&1) ans=fmul(ans,x,MOD);
return ans;
}
bool MR(ll n)
{
if (n==1) return 0;
for (int i=1;i<=9;i++)
{
if (n==prm[i]) return 1;
if (n<prm[i]) return 0;
ll m=n-1,power=fpow(prm[i],m,n);
while (power==1 && !(m&1))
m>>=1,power=fpow(prm[i],m,n);
if (power!=1 && power!=n-1) return 0;
}
return 1;
}
int main()
{
while (scanf("%lld",&n)!=EOF)
if (MR(n)) printf("Y\n");
else printf("N\n");
return 0;
}