各种友(e)善(xin)数论总集,从入门到绝望2---快速判断素数
文章目录
- 前置芝士
- 快速乘
- O(1)
- O(log)
- floyd提出的判环法
- 判环
- 找环
- 生日悖论
- Miller_rabin
- 前言
- 二次探测
- 代码
- Pollard-Rho
- 前言
- 例题
- 构建随机数列
- 优(ka)化(chang)
- 注意事项
- 时间复杂度证明
- 代码
因为原来的那篇已经很多了,所以在此写上第二篇。
这一章可以说是紧紧围绕的素数的主旨展开的。
前置芝士
快速乘
博主博主,平常 O ( 1 ) O(1) O(1)都已经如此之快,难道可以 O ( 0 ) O(0) O(0)?
不不不,都一样,只不过算的是 x ∗ y % z x*y\%z x∗y%z,因为有时候 x ∗ y x*y x∗y溢出了long long,但是结果并没有,所以发明了快速乘。
O(1)
O ( 1 ) O(1) O(1)版的非常简单。 x ∗ y % z = x ∗ y − ( x ∗ y / z ) ∗ z x*y\%z=x*y-(x*y/z)*z x∗y%z=x∗y−(x∗y/z)∗z(在C++环境下),但是这个有什么特殊的吗?
就是用溢出对待溢出,我们先用long double(16位)得出(x*y/z)(你在比赛的时候,可以先用 s i z e o f ( l o n g sizeof(long sizeof(long d o u b l e ) double) double)得出你电脑的long double是几位的,如果不是 16 16 16位的话,那评测机应该也是,为了稳妥还是用 l o g log log的吧)。
然后我们乘一下,两边可能会溢出,但是我们还是能减出正确的结果。
另外还有一坨精度问题,模数大的话还是少用吧。
inline LL ksc(LL x,LL y,LL z)
{LL c=(LD)x*y/z+0.5;LL ans=x*y-c*z;return ans<0?ans+z:ans;
}
O(log)
有没有什么稳得一批又好用的快速乘?
当然后,假设又有个 x , y , z x,y,z x,y,z。
我们把 x x x拆成二进制: c n ∗ 2 n + c n − 1 ∗ 2 n − 1 + . . . . c_{n}*2^{n}+c_{n-1}*2^{n-1}+.... cn∗2n+cn−1∗2n−1+....,而 c c c的取值只能为 0 , 1 0,1 0,1
然后 x ∗ y x*y x∗y在乘法分配率一下: y ∗ c 0 ∗ 2 0 + y ∗ c 1 ∗ 2 1 + . . . y*c_{0}*2^{0}+y*c_{1}*2^{1}+... y∗c0∗20+y∗c1∗21+...,那么我们只要边乘边模不就好起来了吗。
inline ll ksc(ll x,ll y,ll p){//计算x乘y的积ll res=0;//加法初始化while(y){if(y&1)res=(res+x)%p;//模仿二进制x=(x<<1)%p; y>>=1;//将x不断乘2达到二进制}return res;
}
// ll 表示 long long
//这里的代码用的是https://www.cnblogs.com/812-xiao-wen/p/10543023.html
floyd提出的判环法
判环
你以为是floyd,不是,是这样的,假设一个链表有环,怎么判环,我们这样想: y y y以 x x x两倍的速度奔跑,那么当 x , y x,y x,y相遇时, y y y刚好跑完几圈了,就退出。
找环
其实还有个扩展,如何找到环的起始位置。
我们设链表头走到环开始的地方步数为 m m m,从链表头走到相遇地点的步数是 m + k m+k m+k,然后环的长度为 n n n, x , y x,y x,y分别走了 X , Y X,Y X,Y圈。
那么 S x = m + k + X n , S y = m + k + Y n S_{x}=m+k+Xn,S_{y}=m+k+Yn Sx=m+k+Xn,Sy=m+k+Yn,然后又因为 S y = 2 S x S_{y}=2S_{x} Sy=2Sx,所以 S x = ( Y − X ) n S_{x}=(Y-X)n Sx=(Y−X)n。
也就是说两人走的距离肯定是 n n n的倍数。
然后我们再把 x x x提到了链表开始的地方,让两个人继续开始走,当走了 m m m步( x x x在环开始的地方),那么因为说了 S y = 2 S x = 2 ( Y − X ) n S_{y}=2S_{x}=2(Y-X)n Sy=2Sx=2(Y−X)n,也就是说 y y y走的步数应该是 n n n的倍数,也就是说当第一次相遇的时候,他应该离走完这个环到环开始的地方为 m m m,所以 y y y也到了环开始的地方,所以 x , y x,y x,y将会在环开始的地方相遇。
可惜这里不用。
生日悖论
首先我们来看看,现在我们来选数字,在1-100之间,如果我们选一个数字,那么是1的概率则是 1 100 \frac{1}{100} 1001,但是如果我们选两个数字,然后取差的绝对值,会怎样?我们选一个数字,然后选到他周围的两个数字的概率就变成了 1 50 \frac{1}{50} 501了!(忽略第一个数字 1 1 1和 100 100 100的情况,那还是 1 100 \frac{1}{100} 1001),难道多元能增加概率!
没错,这就是生日悖论的内容。
生日悖论的重要思想是什么, 1 − x 1-x 1−x的范围,如果有 x \sqrt{x} x 个数字的话,重复的概率就会高达 50 % 50\% 50%,恐不恐怖。
至于证明,在这里贴上大佬的证明。
Miller_rabin
相信在第一章里面,你们已经学会了费马小定律了,那就不讲了QMQ。
前言
我们都知道判断一个数字是不是素数,有一种方法就是试除法,直接从 2 2 2枚举到 p \sqrt{p} p ,但是有没有一种方法,能比 O ( p ) O(\sqrt{p}) O(p )还快有准确无误呢?
答案是并没有,但是如果你要求的是很大概率的话,打我可以告诉你的是,Miller_rabin就是这么一种算法,基本上准确无误,就连强伪素数都能跑过去,是什么呢?
二次探测
我们都知道,选取一个 p p p数字,然后用费马小定理判断一下,如果不是 1 1 1,那就不是素数,但是存在这么一种数字,能满足费马小定理但是不是素数的一类数字,我们又要怎么判断呢?
这里就要引入一个定理了,这个定理可以很大概率的判断是不是素数,加上费马小定理。
如果 p p p是质数且 a 2 ≡ 1 ( m o d p ) ( a < p ) a^2≡1(\mod p)(a<p) a2≡1(modp)(a<p),那么 a = 1 , p − 1 a=1,p-1 a=1,p−1。
我们可以来证明一下:
a 2 ≡ 1 ( m o d p ) a^2≡1(\mod p) a2≡1(modp)
a 2 − 1 ≡ ( m o d p ) a^2-1≡(\mod p) a2−1≡(modp)
( a + 1 ) ( a − 1 ) ≡ 0 ( m o d p ) (a+1)(a-1)≡0(\mod p) (a+1)(a−1)≡0(modp)
那么,因为 a < p a<p a<p且 p p p是质数,所以 a = 1 , p − 1 a=1,p-1 a=1,p−1。
那么我们就可以把 p − 1 p-1 p−1分成 2 t ∗ k 2^{t}*k 2t∗k,然后随机选取一个值 x x x,然后计算 x k x^{k} xk,然后继续不断取平方: x 2 i ∗ k x^{2^{i}*k} x2i∗k,然后不断的用二次探测来检测,更重要的是我们最后还可以用用费马小定理,当然, x x x我们可以手动取几个素数来多判几次,不知道为什么,素数成功概率大一点QMQ。
很明显是 l o g log log的。
代码
inline LL ksc(LL x,LL y,LL z)
{LL c=(LD)x*y/z+0.5;LL ans=x*y-c*z;return ans<0?ans+z:ans;
}
inline LL ksm(LL x,LL m,LL mod)
{if(m==0)return 1%mod;LL ans=1;while(m>1){m&1?ans=ksc(ans,x,mod):0;x=ksc(x,x,mod);m>>=1;}return ksc(ans,x,mod);
}
inline int log2(LL &x)
{int ans=0;while(x%2==0)ans++,x>>=1;return ans;
}
int su[]={2,3,5,7,11,23,29,61};
inline bool pd(LL x)//判断一个素数
{for(int i=0;i<=7;i++){if(x<=su[i])return 1;LL y=x-1;int tt=log2(y);y=ksm(su[i],y,x);while(tt--){LL z=ksc(y,y,x);if(z==1 && y!=1 && y!=x-1)return 0;y=z;}if(y!=1)return 0;}return 1;
}
Pollard-Rho
前言
你是否想快速的分解一个素数?
想吗?少年。
—来自SaDiao博主的一席话。
例题
都看到了,就是想咯QMQ。
例题
假设我们要分的数字是 p p p
构建随机数列
我们构建一个随机函数 x i = x i − 1 ∗ x i − 1 + C ( m o d p ) x_{i}=x_{i-1}*x_{i-1}+C(\mod p) xi=xi−1∗xi−1+C(modp)( C C C为我们自己定的常数),这么强?
同时 x 1 = 2 x_{1}=2 x1=2,我们发现这个序列每个都跟前面的数字有关,那么不就是类似链表了吗?而且因为是模了后序列,所以会有环(根据生日悖论,出现相同的数字概率是 O ( p ) O(\sqrt{p}) O(p )),就可以用 f l o y d floyd floyd判环法了。
我们再设一个函数: y i = x i − 1 ∗ x i − 1 + C ( m o d q ) y_{i}=x_{i-1}*x_{i-1}+C(\mod q) yi=xi−1∗xi−1+C(modq)( q q q为 p p p的一个质因数), y 1 = 2 y_{1}=2 y1=2,那么这个序列也会出现循环节的,我们再在两个循环节上找到两个位置 i , j i,j i,j,使得 i < j , l ( i ) = l ( j ) i<j,l(i)=l(j) i<j,l(i)=l(j),然后我们就会发现 ∣ x i − x j ∣ |x_{i}-x_{j}| ∣xi−xj∣含有 q q q,也就是 g c d ( ∣ x i − x j ∣ , p ) ≠ 1 gcd(|x_{i}-x_{j}|,p)≠1 gcd(∣xi−xj∣,p)=1,那么我们就可以找到一个素数了。
但是我们并不知道 q q q,我们又怎么找到 i , j i,j i,j呢,我们会发现其实 i , j i,j i,j就是 y y y数列循环节上的对应位置,而上面也只是提供了一种可行性,也就是说我们可以用 f l o y d floyd floyd找环法来在 x x x数列中找,如果 g c d gcd gcd为 1 1 1,那么继续找环,如果 g c d gcd gcd为 p p p(差为0就会这样),说明我们找到了模数为 q q q的环,可惜也是模数为 p p p的环,那么我们就退出,然后 C + + C++ C++,如果两个都不是,那么我们不就找到了一个质因数了吗?
再看看概率有多大,原本我们找两个数字的差找到质因数的概率应该比较小,但是如果我们是 g c d ≠ 1 gcd≠1 gcd=1的话,那概率不就大了吗?而且期望的循环节大小为 p \sqrt{p} p ,不就好起来了吗?
而且判断一个数字是不是素数就靠Miller_rabin了。
优(ka)化(chang)
我们要发现一个事情: x % z x\%z x%z,可以等同于 z ∗ ( ( x / g c d ( x , z ) ) % ( z / g c d ( x , z ) ) ) z*((x/gcd(x,z))\%(z/gcd(x,z))) z∗((x/gcd(x,z))%(z/gcd(x,z)))。
那么这有什么用呢?我们可以把几个差乘起来,然后模一下(如果出现了质因子的话不会因为模了而消失掉,上面写了),至于乘几次,我选择的是 127 127 127,当然, p o w ( p , 0.1 ) pow(p,0.1) pow(p,0.1)也有人用,不要太大就可以了,我们后面则叫乘了 s t e p step step次。
那么我们也是乘完后GCD。
- 如果为 1 1 1继续。
- 如果为 n n n的话,说不定中间有质因子呢,我们也回到 s t e p step step次一起,不乘起来,一次次慢慢来。
- 如果两个都不是,恭喜,喜提质因子一枚。
然后自我感觉良好,优化了不少的常数。
注意事项
我们会发现,打完代码有事后还是会卡住的。
为什么,因为 4 4 4是个神奇的数字,我们可以把 C C C从 1 1 1到 4 4 4枚举一遍,会发现差统统不会涉及到 2 2 2,而其他 2 2 2的次方,比如 2 i 2^{i} 2i,在 C = 2 i − 4 C=2^{i}-4 C=2i−4的时候,肯定跳一次就能得到结果, x = 0 , y = C x=0,y=C x=0,y=C,然后就可以把 2 2 2筛出来,为什么 4 4 4不行,因为 C = 0 C=0 C=0了,所以我们就只会一遍遍的得到 4 4 4然后重来。
那我们特判 4 4 4不就行了?不不不,特判要讲究艺术。
你想想,我们要是特判 % 2 = = 0 \%2==0 %2==0不是一样的吗,而且还造福了其他的数字,尤其是 2 2 2的次方,不加这个很有可能就老是到 2 i − 4 2^{i}-4 2i−4才跳出来。
时间复杂度证明
一次的Pollard-Rho的复杂度是多少?(这道题目得用几次Pollard-Rho)
设 N = A ∗ B ( A < = B ) N=A*B(A<=B) N=A∗B(A<=B),那么 A < = ( N ) A<=\sqrt(N) A<=( N),我们是在 x x x找 A A A的循环节,期望复杂度为 O ( A ) O(\sqrt{A}) O(A ),那么不就是 O ( N 1 4 ) O(N^{\frac{1}{4}}) O(N41)吗?
但是其实不然,因为加上GCD什么乱起八糟的,正宗的应该是:
O ( N 1 4 l o g N s t e p + l o g N ) O(\frac{N^{\frac{1}{4}}logN}{step}+logN) O(stepN41logN+logN),但原本就是玄学算法你加这么多干嘛,而且在long long范围内我们的 s t e p step step肯定大于 l o g N logN logN,毕竟我们的 s t e p step step原本就是 127 127 127吗。
所以我们还是写 O ( N 1 4 ) O(N^{\frac{1}{4}}) O(N41),好看又好写。
代码
开了O2在luogu跑了600+ms,快的飞起,不开也有1.44s了,这不就快的飞起了吗。
可以试试这个跑不跑得出结果46856248255981。
强伪素数呀,都跑过去了。
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
typedef long double LD;
typedef long long LL;
inline LL zabs(LL x){return x<0?-x:x;}
inline LL mymin(LL x,LL y){return x<y?x:y;}
inline LL mymax(LL x,LL y){return x>y?x:y;}
inline LL ksc(LL x,LL y,LL z)
{LL c=(LD)x*y/z+0.5;LL ans=x*y-c*z;return ans<0?ans+z:ans;
}
inline LL ksm(LL x,LL m,LL mod)
{if(m==0)return 1%mod;LL ans=1;while(m>1){m&1?ans=ksc(ans,x,mod):0;x=ksc(x,x,mod);m>>=1;}return ksc(ans,x,mod);
}
inline int log2(LL &x)
{int ans=0;while(x%2==0)ans++,x>>=1;return ans;
}
int su[]={2,3,5,7,11,23,29,61};
inline bool pd(LL x)//判断一个素数
{for(int i=0;i<=7;i++){if(x<=su[i])return 1;LL y=x-1;int tt=log2(y);y=ksm(su[i],y,x);while(tt--){LL z=ksc(y,y,x);if(z==1 && y!=1 && y!=x-1)return 0;y=z;}if(y!=1)return 0;}return 1;
}
inline LL gcd(LL x,LL y)//实测二进制版GCD只比原来的快了20+ms,估计是因为优化减少了GCD的调用次数,凸显不出优势。
{int ans=0;while(x && y){if(x&1 && y&1){y>x?x^=y^=x^=y:0;x=(x-y)>>1;}else if(x&1)y>>=1;else if(y&1)x>>=1;else x>>=1,y>>=1,ans++;}return (x+y)<<ans;
}
inline LL Pol(LL now,LL step,LL add)
{if(now%2==0)return 2;//防止毒瘤的4的情况 LL x=2,y=2,d=1;while(1){LL tx=x,ty=y;for(int i=1;i<=step;i++){x=ksc(x,x,now)+add;x>=now?x-=now:0;y=ksc(y,y,now)+add;y>=now?y-=now:0;y=ksc(y,y,now)+add;y>=now?y-=now:0;d=ksc(d,zabs(x-y),now);}d=gcd(d,now);if(d==1)continue;else if(d!=now)return d;x=tx;y=ty;for(int i=1;i<=step;i++){x=ksc(x,x,now)+add;x>=now?x-=now:0;y=ksc(y,y,now)+add;y>=now?y-=now:0;y=ksc(y,y,now)+add;y>=now?y-=now:0;d=gcd(zabs(x-y),now);if(d!=1)return d%now;}}
}
inline LL work(LL n)
{if(pd(n) || n==1)return n;LL tmp=0,step=127/*玄学步数*/,add=1;while(!tmp)tmp=Pol(n,step,add++);//if(n/tmp<tmp)tmp=n/tmp;//使得n/tmp>=tmpLL ans=work(n/tmp);if(ans>=tmp)return ans;return mymax(ans,work(tmp));//实现上的一个优化,优化空间小,但是能优化,而且不会耗多少空间,基本正优化
}
LL n;
int main()
{int T;scanf("%d",&T);while(T--){scanf("%lld",&n);LL ans=work(n);if(ans==n)printf("Prime\n");else printf("%lld\n",ans);} return 0;
}
各种友(e)善(xin)数论总集,从入门到绝望2---快速判断素数相关推荐
- 各种友(e)善(xin)数论总集(未完待续),从入门到绝望
目录 快速幂 扩展欧几里得 GCD 扩展欧几里得 同余系列 同余方程 同余方程组 一点想法 高次同余方程 BSGS exBSGS 线性筛素数 埃式筛 欧拉筛 欧拉函数 讲解 两道水题 法雷级数 可见点 ...
- 各种友(e)善(xin)数论总集,从入门到绝望2
目录 前置芝士 二进制的GCD 快速乘 O(1) O(log) floyd提出的判环法 判环 找环 生日悖论 Miller_rabin 前言 二次探测 代码 Pollard-Rho 前言 例题 构建随 ...
- ACM数论之旅2---快速幂,快速求a^b
如果题目说数据很大,还需要求余,那么代码就可以这么写 LL pow_mod(LL a, LL b){//a的b次方LL ret = 1;while(b != 0){if(b % 2 == 1){ret ...
- 数论的基础入门(初读数论概论有感)(acm知识储备)
在寒假自己对自己的硬核知识进行了充电,这本书是学长极力推荐的,是专门给数学系的学生进行对数论进行全面认识和理解的入门书籍,虽然自己是计算机的,但是并不影响去阅读它,读的过程中就发现了,书中不少数学系的 ...
- 友盟+消息推送U-Push为无他相机 提供快速、高并发的推送服务
在"颜值即正义"的今天,相机类App已经成为让人变美的刚需类应用工具.自拍分享.假日游玩.美食推荐......相机类App已经深入到用户的生活点滴.瘦脸,拉腿,换滤镜,加贴纸,一张 ...
- 数学--数论--Miller_Rabin判断素数
ACM常用模板合集 #include<iostream> #include<algorithm> #include<cstring> #include<cst ...
- 数论 判断素数:普通素数判别 线性筛 二次筛法求素数 米勒拉宾素数检验
普通的素数判断法 当我们要判断一个数字是否是素数的时候,往往会直接看这个数字模1到这个数字的根号,看有没有等于零的,从而判断这个数字是不是素数,这样做的时间复杂度为O(sqrt(n)) bool is ...
- `Computer-Algorithm` 数论基础知识 (同余,取模,快速幂,质数,互质,约数,质因子)
catalog 同余 取模 快速幂 质数 互质 约数 质因子 @Delimiter(旧解释) 经验谈 两数之差也整除 加一的特殊性 取模 累加的周期性 取模的唯一集合 取模下的四则运算 除法的不可约性 ...
- 素数一套:Miller-Rabin 素性检验算法Pollard-Rho算法线性筛——Upside down primesDivisions
部分目录 Solved 94 / 304 K Gym 100753K Upside down primes 高效判断素数 快速幂取模 继续 Miller-Rabin 素性检验算法 Unsolved 6 ...
最新文章
- 【Ubuntu】在Ubuntu中设置永久的DNS
- 洛谷P1433 吃奶酪【dfs】【剪枝】
- JVM-11虚拟机性能监控与故障处理工具之【JDK的可视化工具-JConsole】
- HTML5的基本入门格式介绍
- python为什么这么小_同样是 Python,怎么区别这么大
- 更适合Pythoner的标记语言Yaml学习总结
- 华南农业大学c语言上机实验答案,华南农业大学C语言上机实验答案.doc
- 微信蓝牙协议一:协议文档查阅方法和空中数据解析示例
- 树莓派 口罩识别 python_RaspberryPi上实现佩戴口罩识别——2020电赛F题小记
- 单片机c语言直接寻址 间接寻址,pic单片机教程之数据存储器的直接间接寻址方式...
- 安徽大学2020年计算机考研,2020年安徽大学计算机专业课初试科目调整
- 洛谷B2006 地球人口承载力估计
- 计算机专利英语笔译,基于Trados2014的专利翻译实践报告-英语笔译专业论文.docx...
- 使用easypoi操作excel
- exadata的exacli
- TunesKit Audio Converter for Mac(音频格式转换软件)
- 2020年10月25日总结
- 亿级短视频社交美拍架构实战
- 怎么搭建云平台的服务
- linux生成ssl证书脚本
热门文章
- Magisk搞机器记录(小米Mix3)
- 计算机 计算能力测试题,外貌年龄计算器测试题
- c语言指针flash,STM32F103RCT6之FLASH读写操作
- 深圳茁壮IPANEL浏览器中间件 debug模块移植参考,打印分级等功能,可以移到其他嵌入式系统
- 事件学习——1. 事件的认识
- 开集(Open Set)、闭集(Closed Set)和紧集(Compact Set)
- UA SIE545 优化理论基础1 例题4 证明一个集合是闭集
- 已解决WARNING: Ignoring invalid distribution -addlepaddle (d:softpython36libsite-packages)
- 位置不可用无法访问文件或目录损坏且无法读取
- 思维导图制作软件/网站