题面
题解
Miller-rabin素数测试
是一种随机化算法,能够在较短的时间内判断出一个数是合数,还是很可能为素数。其出错的概率极小;当用于测试的p取遍前10个素数时,在\(3e18\)的范围内不会出错。
原理:1、费马小定理
p为素数的必要不充分条件是\(\forall (a,p)=1,a^{p-1}{\equiv}1{\mod p}\)。证明不再赘述,可以参见百度百科或《数学奥林匹克命题人讲座:初等数论》2.3节。
2、二次探测
只使用费马小定理时,出错概率仍然较大,尤其是对于Carmichael数n(在\(1e8\)以下有255个,最著名的、最小的Carmichael数是561),对于所有的与n互质的正整数a,都可以满足\(a^{n-1}{\equiv}1{\mod n}\)。因此,仅仅使用Fermat测试是不够的。
二次探测的原理是:如果\(x^2{\equiv}1{\mod p}\),p为素数,那么有\(x{\equiv}{\pm}1{\mod p}\)。证明显然,将1移项至左边,然后因式分解即可。
具体做法
设待测数为mod,若mod为偶数则可以直接判断。否则,我们将mod-1分解为\(2^t*n\),其中\(2{\nmid}n\)。然后,取与mod互质的数p,计算
\[p^n、 p^{2n}、 p^{2^2n} … p^{2^tn}{\% mod}\]
从第二个数开始,每一个数可以通过前一个数平方得到。
- 如果最后一项不为1,那么根据原理1,p不是素数;
- 如果第一项就为1,那么此次测试失败,需要更换p。但是,这样的概率极小。
- 如果第一项不为1,且最后一项为1:我们寻找这个数列中的第一个1,其前一项如果不是n-1,那么根据原理2,p不是素数。
对于特定选取的p,经过一次test(p,mod)后,出错的概率在\({\frac{1}{4}}\)以下。证明见《算法导论》第31.8节。
在待测数\(mod{\leq}3e18\)的情况下,选取p为2,3,5,7,11,13,17,19,23这前9个素数,可以保证不出错。
对于一个待测数mod,Miller-rabin素数测试的时间复杂度为\(O(klogmod)\)。(其中k为测试的轮数)
防止相乘爆long long的小技巧
- 此方法适用范围:x,y,mod均在\(1e18\)级别。
#define ll long long #define ld long double inline void Adjust(ll &x,ll mod){ x = (x % mod + mod) % mod; } inline void Tms(ll &x,ll y,ll mod){ x = x * y - (ll)((ld)x * y / mod) * mod; Adjust(x,mod); }
在Miller-rabin素数测试中,由于数据范围很大,会碰到求xy%mod(其中x,y,mod在1e18级别)的问题,此时如果直接乘会爆long long。为了解决这个问题,我们可以利用自然溢出:若两个long long型变量\(x,y\),进行了某种运算\({\bigodot}\)(包括加、减、乘)后超出了\([-2^{63},2^{63})\)的范围,那么返回值是使得
\[x{\bigodot}y=2^{64}*t+r(-2^{63}{\leq}r<2^{63})\]
的r。在Tms()中,\(x*y\)和\((ll)((ld)x * y / mod) * mod\)均属于这种情况。二者的相减也是。
我们设(假设没有溢出)\(x*y-(ll)((ld)x*y/mod)*mod\)的值为r',我们真实想要的数为r。由于long double的运算可能产生误差,所以实际上\(r'\)可能等于r或者\(r{\pm}mod\)。但是无论如何,\(r'\)是\([-2^{61},2^{61}]\)内,与r关于mod同余的一个数。
而考虑到溢出,真实情况下,\(x*y-(ll)((ld)x*y/mod)*mod\)的返回值应该是与\(r'\)关于\(2^{64}\)同余的、在\([-2^{63},2^{63})\)内的一个数\(s\)。但是,我们发现\(r'\)不管是加上还是减去\(2^{64}\),都超出了\([-2^{63},2^{63})\),因此,一定有\(s=r'\)。
然后为了消除可能有的误差,再进行一次Adjust操作即可。
代码
#include<bits/stdc++.h> using namespace std; #define ll long long #define ld long double #define rg register namespace ModCalc{ inline void Adjust(ll &x,ll mod){ x = (x % mod + mod) % mod; } inline ll Check(ll x,ll mod){ Adjust(x,mod);return x; } inline void Tms(ll &x,ll y,ll mod){ x = Check(x * y - (ll)((ld)x*y/mod) * mod,mod); } inline ll Mul(ll x,ll y,ll mod){ Tms(x,y,mod);return x; } } using namespace ModCalc; ll pri[9] = {2,3,5,7,11,13,17,19,23}; inline ll power(ll a,ll n,ll mod){ ll x = a,s = 1; while(n){ if(n & 1)Tms(s,x,mod); Tms(x,x,mod); n >>= 1; } return s; } inline bool test(ll mod,ll p){ if(mod == p)return 1; //mod=p需要特判,因为p的倍数一定不符合费马小定理 ll n = mod - 1; while(!(n&1))n >>= 1; ll r = power(p,n,mod); if(r == 1)return 1; //运气不好,本轮测试失败 while(n < mod - 1){ ll x = Mul(r,r,mod); if(x == 1)return r == mod - 1; r = x,n <<= 1; } return 0; } inline bool MR(ll mod){ if(mod < 2)return 0; if(!(mod&1))return mod == 2; for(rg ll i = 0;i < 9;i++)if(!test(mod,pri[i]))return 0; return 1; } int main(){ ll mod; while(~scanf("%lld",&mod)){ puts(MR(mod) ? "Y" : "N"); } return 0; }
来源:https://www.cnblogs.com/xh092113/p/12288424.html