题目
题目链接:https://loj.ac/p/143
给定一个数 (n),判断是否是质数。
(nleq 10^{18})。
思路
Miller–Rabin 素数测试板子题。推荐博客。
质数有两个性质。一是众所周知的费马小定理:若 (p) 是质数,且 (x) 不是 (p) 的倍数,则
[x^{p-1}equiv 1pmod p
]
还有一个二次探测定理。如果 (p) 是质数且 (x^2equiv 1pmod p),那么 (xequiv1pmod p) 或 (xequiv p-1pmod p)。
证明直接把 (1) 移项平方差即可。
所以对于一个要测试的数 (p),我们取一个数 (a),先判断 (a^{p-1}mod p) 是否等于 (1)。如果不是那么显然 (p) 是合数。
接下来重复此操作:
- 如果 (p-1) 是奇数则直接退出循环。
- 计算 (a^{frac{p-1}{2}}mod 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
");
else printf("N
");
return 0;
}