开坑时间:2019.10.18周五
学习原因及其他
没什么原因,就是想学。有可能是因为今天在机房,csl到处问Pollard’s rho怎么写,我随即发现自己不会,决定去学习。
2019-10-18
入门
入门,初步学习:xyx的博客
初步了解Pollard’s rho的过程。认识到它的本质以及大致过程。
- 要分解 N N N* 伪随机生成数列 A = { a 1 , a 2 , a 3 , . . . } A=\{a_1,a_2,a_3,...\} A={a1,a2,a3,...},每一项由前一项用固定方式推得。常用方式为 a i = f ( a i − 1 ) = ( a i − 1 2 + C ) m o d N a_i=f(a_{i-1})=(a_{i-1}^2+C)mod\,N ai=f(ai−1)=(ai−12+C)modN其中 C C C为一不变常数。
- 每次查看 g c d ( a 2 i − a i , N ) gcd(a_{2i}-a_i,N) gcd(a2i−ai,N),若不为1,说明此时找到了一个约数 g c d ( a 2 i − a i , N ) gcd(a_{2i}-a_i,N) gcd(a2i−ai,N)。若此时 g c d ≠ N gcd\neq N gcd=N,说明我们确实找到了一个非平凡约数。但如果 g c d = N gcd=N gcd=N,就没有找下去的必要了。
- 这实际上是找到了循环节。即,如果最终我们有一个因数 P P P,那么 a i m o d P a_i\,mod\,P aimodP实际是有循环节的。当 g c d ≠ 1 gcd\neq 1 gcd=1是,我们发现 a 2 i a_{2i} a2i与 a i a_i ai实际上就到了循环节上一个相同的位置。
- 如果此时 g c d = N gcd=N gcd=N,就说明 a 2 i a_{2i} a2i与 a i a_i ai也到了 m o d N mod\,N modN循环节上的同一位置。这就说明不行了,我们要换一个 C C C。
- 我们假设要经过 Q Q Q步进入循环节,循环节长 R R R步,那么 a 2 i a_{2i} a2i与 a i a_{i} ai要对应上循环节同一点,所需时间是 O ( Q + R ) O(Q+R) O(Q+R)的。* 所以这个算法的关键就是期望多少次, a i m o d P a_i\,mod\,P aimodP会冲突。生日悖论告诉我们是 O ( P ) O(\sqrt{P}) O(P)即 O ( N 1 4 ) O(N^{\frac{1}{4}}) O(N41)
生日悖论
进一步学习:关于生日悖论发现一篇文章:一篇文章这篇文章关于生日悖论讲了一些。可以帮助理解为什么会在很快的时间内成功,但是没有证明期望。
关于名字
发现了为什么要叫Pollard’s rho:因为循环节这东西长得很像 ρ \rho ρ
关于 a 2 i − a i a_{2i}-a_i a2i−ai
这东西实际上就是Floyd的找环算法。
2019-10-21
代码1
LL PR_zuo(LL cn,LL a)
{
LL lin1 = 2, lin2 = get_ne(lin1,a,cn), lin3;
while(1)
{
lin3 = gcd((lin2-lin1+cn)%cn, cn);
if(lin3 != 1) return lin3 == cn ? 0 : lin3;
lin1 = get_ne(lin1,a,cn); lin2 = get_ne(get_ne(lin2,a,cn),a,cn);
}
}
LL Pollard_rho(LL cn)
{
if(cn == 1) return 1;
if(!(cn&1)) return 2;
if(Miller_Rabin(cn)) return cn;
LL a = 4, lin;
while(1) {if(lin = PR_zuo(cn,a)) return lin; a += 3; }
}
这份代码时间复杂度 O ( N 1 4 α ( N ) ) O(N^{\frac{1}{4}}\alpha(N)) O(N41α(N)) ,其中 α ( N ) \alpha(N) α(N)表示求 O ( N ) O(N) O(N)大小的两个数的gcd的时间复杂度。
代码2
LL PR_zuo(LL cn,LL a)
{
LL lin1 = 2, lin2 = get_ne(lin1,a,cn), lin3;
int B = 100;
while(1)
{
LL lin = 1, lin4 = lin1, lin5 = lin2;
for(int i = 1;i<=B;i++)
{
lin = ksc(lin,(lin5-lin4+cn)%cn,cn);
lin4 = get_ne(lin4,a,cn); lin5 = get_ne(get_ne(lin5,a,cn),a,cn);
}
lin3 = gcd(lin,cn);
if(lin3 != 1) {if(lin3 != cn) return lin3; break; }
lin1 = lin4; lin2 = lin5;
}
while(1)
{
lin3 = gcd((lin2-lin1+cn)%cn, cn);
if(lin3 != 1) return lin3 == cn ? 0 : lin3;
lin1 = get_ne(lin1,a,cn); lin2 = get_ne(get_ne(lin2,a,cn),a,cn);
}
}
LL Pollard_rho(LL cn)
{
if(cn == 1) return 1;
if(!(cn&1)) return 2;
if(Miller_Rabin(cn)) return cn;
LL a = 4, lin;
while(1) {if(lin = PR_zuo(cn,a)) return lin; a += 3; }
}
这份代码是 O ( N 1 4 + N 1 4 B α ( N ) + B α ( N ) ) O(N^{\frac{1}{4}}+\frac{N^{\frac{1}{4}}}{B}\alpha(N)+B\alpha(N)) O(N41+BN41α(N)+Bα(N)), B B B是一个选定的常数。
但是我这一份慢得很,不知道为什么。正在努力卡常。
2019-10-27
一个最新进展:我发现 a a a初值选得好,会让程序效率有一定的提升(大概20%)
13是一个不错的选择。