Miller_Rabin素性测试学习小记

问题

  • 给出一个正整数n,判断它是不是质数。
  • 有一个简单暴力的方法:试除法,从2枚举到 n \sqrt n n ,如果有一个数能整除n,则表明n为合数。
  • 但这么做的时间复杂度是 O ( n ) O(\sqrt n) O(n )的,当n的规模达到 1 0 16 10^{16} 1016甚至更高时,基本上就会跑1s以上了。
  • 有什么更快的方法?

费马小定理

  • 通过费马小定理我们知道,对于一个质数p,有: a p − 1 ≡ 1   ( m o d   p ) , 1 ≤ a &lt; p a^{p-1}\equiv1\ (mod\ p),1≤a&lt;p ap11 (mod p),1a<p
  • 我们只要找到一个a,使p不满足上述性质,就可以肯定p为合数,而这个a就成为p的证据。

  • However,存在这样一些p,它对于 ∀ a ∣ ( a , p ) = 1 \forall a|(a,p)=1 a(a,p)=1,均满足 a p − 1 ≡ 1   ( m o d   p ) a^{p-1}\equiv1\ (mod\ p) ap11 (mod p)
  • 譬如 p = 516 = 3 ⋅ 11 ⋅ 17 p=516=3·11·17 p=516=31117(不信手算一下)。这种数被称为 Carmichael 数。
  • 如果你碰巧随机到了这些数的因子,比如随机到a=3,你会发现 3 561 − 1 ≡ 375 ̸ ≡ 1   ( m o d   561 ) 3^{561-1}\equiv375\not\equiv1\ (mod\ 561) 35611375̸1 (mod 561)。但它们的因子不多,假如你没随机到,那么就gg了。

二次探测定理强化

  • 考虑强化这个算法。
  • 二次探测定理:若p为质数,a为小于p的正整数,则方程 a 2 ≡ 1   ( m o d   p ) a^2\equiv1\ (mod\ p) a21 (mod p)的解为: a ≡ ± 1   ( m o d   p ) a\equiv±1\ (mod\ p) a±1 (mod p)
  • 因为 a 2 ≡ 1   ( m o d   p ) a^2\equiv1\ (mod\ p) a21 (mod p),故 p ∣ ( a 2 − 1 ) p|(a^2-1) p(a21),即 p ∣ ( a + 1 ) ( a − 1 ) p|(a+1)(a-1) p(a+1)(a1)。那么囿于p为质数,p肯定与a+1或a-1互质,那么直接套欧几里得引理, p ∣ ( a + 1 ) p|(a+1) p(a+1) p ∣ ( a − 1 ) p|(a-1) p(a1),即 a ≡ ± 1   ( m o d   p ) a\equiv±1\ (mod\ p) a±1 (mod p)

  • 若p为奇素数,那么记 p − 1 = 2 k q , 2 ∤ q p-1=2^kq,2∤q p1=2kq,2q
  • a ∤ q a∤q aq,则下面两项条件至少一项必然成立:
  • a q ≡ 1   ( m o d   p ) a^q\equiv1\ (mod\ p) aq1 (mod p)
  • a q , a 2 q , a 2 2 q , … , a 2 k − 1 q ≡ − 1   ( m o d   p ) a^q,a^{2q},a^{2^2q},…,a^{2^{k−1}q}≡−1\ (mod\ p) aq,a2q,a22q,,a2k1q1 (mod p)

  • 因为第二个条件中的数,每一个都是前一个的平方;而根据费马小定理, a 2 k q ≡ a p − 1 ≡ 1   ( m o d   p ) a^{2^kq}\equiv a^{p-1}\equiv1\ (mod\ p) a2kqap11 (mod p)
  • 根据二次探测定理,如果表中一个数模p不余1,但其平方模p余1,则它一定是-1。
  • 因此,在此情况下,表中包含-1,又或者表中全为1,那么第一个条件就会成立。
    #技巧
  • a可以多取几个,这样能使正确率提高。
    这里写图片描述
  • 大概取5个,在 1 0 18 10^{18} 1018范围内就基本上卡不掉了。如果你觉得不够,还可以再多取一些。
  • 当然,根据上述分析,也不一定要取质数,随机一些a也是不错的选择。

Code

ll d[5]={2,3,5,7,10007};
bool miller_rabin(ll k)//判断正整数k是否为素数,如果是返回1,否则返回0
{
	int i; ll t,n;
	fo(i,0,4)
	{
		if(k==d[i]) return 1;
		if(k%d[i]==0) return 0;
		n=k-1;
		while((n&1)==0) n>>=1;
		for(t=fpow(d[i],n,k); n<k-1&&t!=1&&t!=k-1; n<<=1) t=ti(t,t,k);
		if(~n&1&&t<k-1) return 0;
	}
	return 1;
}

黑科技

  • 观察到我的代码里有个t=ti(t,t,k);这其实是t=t*t%k;但是两个 1 0 18 10^{18} 1018的数乘起来会爆long long。
  • 我们可以把乘法改成类似快速幂的加法(因为两个数的乘法可以看做连续的加法),我们称之为微速乘。这样的复杂度就会套个log。不要小看这个log,这个log一般都是 l o g 2 1 0 18 log_210^{18} log21018级别的,基本上是60左右,你原本20ms的就会变成1200ms。
  • 有一种更快的方法:
    这里写图片描述
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值