判断素数的方法(Miller_Rabin)

序言

背景

  • “还有没有判断素数的方法?”
  • “当然有,那就是Miller_Rabin!”
  • 这个算法能做到 O ( l o g n ) O(logn) O(logn)级别,但是相较于普通的 O ( n ) O(\sqrt n) O(n )以及willson定理所需要的神奇的快速求阶乘相比,它的优势也是十分明显的。
  • 但,这一个算法有一个局限性。就是它不能做到判断所有的素数。
  • 这时候就有人开始担心了——这么个正确性玄学的算法,用起来都觉得不安心!
  • 可是,我们要知道,在一般的题目中,我们最多就只会判断大小在long long(大约在 [ 1 , 1 0 19 ] [1,10^{19}] [1,1019])范围内的素数。在一个这么小的范围内,它的正确性可以达到100%。

必备的定理

费马小定理
  • p p p是质数, a m o d    p ≠ 0 a \mod p \neq 0 amodp=0,那么 a p − 1 ≡ 1 ( m o d    p ) a^{p-1} \equiv 1(\mod p) ap11(modp)
  • 具体的证明可以考虑用剩余系,把 [ 1 , p − 1 ] [1,p-1] [1,p1]的数用形如 k ∗ a k*a ka的数来代替。
  • k k k的集合本身也是 [ 1 , p − 1 ] [1,p-1] [1,p1]
二次探测定理
  • 若存在 a a a,使 a 2 ≡ 1 ( m o d    p ) a^2 \equiv 1 (\mod p) a21(modp), a m o d    p ≠ − 1 a \mod p \ne -1 amodp=1 a m o d    p ≠ 1 a\mod p\ne 1 amodp=1,则 p p p是质数。
  • 证明可以考虑反证法, ( a − 1 ) ( a + 1 ) ≡ 0 ( m o d    p ) (a-1)(a+1) \equiv 0(\mod p) (a1)(a+1)0(modp),发现这样是与 p p p为质数矛盾。故定理成立。

Miller_Rabin

  • 我们可以用费马小定理和二次探测定理的“逆定理”来做。
  • 这里之所以要打双引号,是因为它们的条件只具备必要性,是没有逆定理的。
  • 但这样正确性不就玄学了吗?
  • 上面已经说过了,在小范围内正确的概率就会升到100%。
  • 关键是如何提升概率。
  • 显而易见的,我们可以选取多个 a a a,但注意一定要是素数,不然的话有可能不会满足费马小定理中 a a a p p p不是倍数关系的条件。
  • 对于每一个 a a a,我们就用“逆定理”来反过来推断该数是不是素数。
  • 具体流程是这样的:
  • 选取素数{2,3,5,7,…}
  • 对于待测数 p p p,设 p − 1 p-1 p1= 2 s ∗ t 2^s*t 2st t t t为奇数,先求出 d = a t d=a^t d=at
  • 不断平方,若平方到 − 1 -1 1,则二次探测定理已经无效了,直接用费马小定理判断。
  • 若在没有出现过 − 1 -1 1的情况下平方到 1 1 1,则说明 p p p为合数。
  • 若知道平方到 a p − 1 a^{p-1} ap1也没有出现1,则不符费马小定理, p p p为合数。
  • 其余情况,则说明 p p p有可能数素数,选取下一个 a a a判断。
  • 若所有 a a a都试过,没有出现能说明 p p p一定为合数的情况是,认定 p p p为素数。

其它

  • 一般来说,a选9个就绝对没有问题了。
  • 如果待测数太大,要用快速乘,则时间复杂度退化到 O ( l o g 2 n ) O(log^2n) O(log2n),但跑的还是不错的。

代码

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
int a[10]={2,3,5,7,11,13,17,19,23,29};
ll mi(ll x,ll t,ll mod){
	ll d=1;
	while(t){
		if(t%2) d=d*x%mod;
		x=x*x%mod;t/=2;
	}
	return d;
}
bool check(ll x){
	if(x==1) return false;
	ll t=x-1;int p=0;
	while(t%2==0) t/=2,p++;
	for(int i=0;i<=9;i++){
		if(a[i]==x) return true;
		ll t2=mi(a[i],t,x);
		for(int j=1;j<=p;j++){
			if(t2==x-1) {t2=mi(a[i],x-1,x);break;}
			ll t3=mi(t2,2,x);
			if(t3==1&&t2!=1&&t2!=x-1) return false;
			t2=t3;
		}
		if(t2!=1) return false;
	}
	return true;
}
int main()
{
	ll n;scanf("%lld",&n);
	if(check(n)) printf("YES\n");
	else printf("NO\n");
	return 0;
}

总结

  • 素数的世界如此精彩,我们的探索将会一直继续!

(完)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值