0901-Miller_Rabin素数测试算法+例题

版权声明:虽然我只是个小蒟蒻但转载也请注明出处哦 https://blog.csdn.net/weixin_42557561/article/details/82290799

看了好久终于把这个Miller_Rabin搞懂了,觉得自己棒棒哒~~~最后是在下面那篇博客里搞懂的,这里推荐给大家

-->参考<--

【写在前面】

费马定理 and 二次探测<证明来源>

然后费马定理是一个必要条件,也就是说素数一定满足这个定理,但满足这个定理的不一定是素数,比如说Carmichael数(我没研究过,有兴趣的同学自己百度吧,反正这种数就是反例)。

【正文】

了解这两个东西后,我们就可以开始搞事情了

问题引入

这道题就是让我们在给出的整数集合中统计有多少个素数,这个整数集合的大小在int范围以内,大概就是4294967296(哈哈,其实就是1e9级别的)

幼儿园做法:对于集合中的每一个数,我们都用试除法,每个数都会有\sqrt{n}次的枚举,大概就是一个数65536上线,然后还有好多好多的数,妥妥的TLE

小学生做法:机智一点的小可爱会想到什么线性筛,什么埃拉托斯特尼筛法,但是哪一个的复杂度里没有带 n 的呢????又是一个TLE

高中生做法:胡乱的来几下Miller_Rabin,没错就是宇宙无敌超可爱的博主我啦(请自动无视)

通过这几种方法的对比,我们就知道了Miller_Rabin算法的作用,对于很大的一个数快速判断其是否为质数。

但我们要知道Miller_Rabin这个算法是个大概率算法,也就是说不能100%保证验证的正确性,但据说75%的概率准确,而且通常情况下多搞几次,错的概率就很小了

铺垫弄完了,我们正经的讲算法了!!!

假设现在我们需要判断一个数x是否为质数:

若为2,则判断true

若为非2的偶数或1,则直接判断false

若都不是的话,我们就前往下一步

由于费马定理:a^(p-1)≡1(mod p).【p为质数】那如果我们的目标 x 不满足:a^(x-1)≡1(mod x)则x肯定不为质数

然后令 x-1 = u* 2^t ,求出 u,t,其中u是奇数(因为如果u为偶数,那么u中肯定含有因子2,那就可以提到2^t里面去)

变一下形:a^(x-1)≡1(mod x) => a^(u* 2^t )≡1(mod x) => (a^u)^(2^t )≡1(mod x) 【对于a我们是随机取的一个1~x之间的数】

我们令b=(a^u)%x,然后是t次循环,每次循环都让y=(b*b)%x,b=y,这样t次循环之后b=a^(u*2^t)=a^(n-1)了。这个时候二次探测就派上用场了,由于y=(b*b)%x中b是1~x中间的一个数(因为b一直在对x取模),若 y=(b*b)%x = 1,假设x是质数,那么b=1或b=x-1,那反过来如果b!=1且b!=x-1,那x肯定不是素数了

t次循环结束后,由于费马定理,这时如果b!=1,那x就不为素数了

因为Miller-Rabin得到的结果的正确率为 75%,所以要多次循环来提高正确率

最后若循环多次之后还没返回false,那么x肯定是素数了,返回true

【代码】

#include<cstdio>
#include<cmath>
#include<cstring> 
#include<iostream> 
#include<algorithm>
#define ll long long
#define in read()
using namespace std;
ll ans=0,n,x;
inline ll read(){
	char ch;ll res=0;
	while((ch=getchar())<'0'||ch>'9');
	while(ch>='0'&&ch<='9'){
		res=(res<<3)+(res<<1)+ch-'0';
		ch=getchar();
	}
	return res;
}
ll mod_mul(ll a,ll b,ll mod){//快速积(和快速幂思想一模一样)
	ll ans=0;
	while(b){
		if(b&1) ans=(ans+a)%mod;
		a=(a+a)%mod;
		b>>=1;
	}
	return ans;
}
ll mod_pow(ll a,ll b,ll mod){//快速幂
	ll ans=1;
	while(b){
		if(b&1) ans=mod_mul(ans,a,mod);//这个地方要注意一下在这里可以写作ans*a%mod,但更一般的情况下,比如测的数很大很大,此时乘的时候可能会爆long long,而用函数的时候就边乘边取模,就不会爆了
		a=mod_mul(a,a,mod);
		b>>=1;
	}
	return ans;
}
bool miller(ll x){//由于和讲解时使用的符号都是统一的,所以我就不再批注了
	int i,j,k,s=10;//s是循环的次数,用来提高正确率的那个
	ll u=x-1,t=0;
	if(x==2) return true;
	if(!(x&1)||x<2) return false;
	while(!(u&1)){//如果为偶数,就一直除以2,直到u为奇数
		t++;
		u>>=1;
	}
	for(i=1;i<=s;++i){
		ll a=rand()%(x-1)+1;//随机搞
		ll b=mod_pow(a,u,x),y;
		for(j=0;j<t;++j){
			y=mod_mul(b,b,x);
			if(y==1&&b!=1&&b!=x-1)
				return false;
			b=y;
		}
		if(b!=1) return false;
	}
	return true;
}
int main()
{
	while(scanf("%lld",&n)!=EOF){
		ans=0;
		int i,j,k;
		for(i=1;i<=n;++i){
			x=in;
			if(miller(x)) ans++;
		}
		cout<<ans<<endl;
	}
	return 0;
}
 

对于评论区我进行解释:

由于这道题我是作为Miller_Rabin的板子题,就把它当做最一般的情况进行处理的,并没有太考虑数据范围

所以有些地方针对此题是可以简写的,甚至用幼儿园做法

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/82290799