【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\) 是合数。
接下来重复此操作:

  1. 如果 \(p-1\) 是奇数则直接退出循环。
  2. 计算 \(a^{\frac{p-1}{2}}\bmod p\),根据二次探测定理,如果答案不是 \(1\)\(n\)\(p\) 是合数。
  3. 如果答案是 \(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;
}
posted @ 2021-01-19 11:16  stoorz  阅读(146)  评论(0编辑  收藏  举报