因式分解
Pollard-Rho大整数分解
对于任意正整数n,由算术基本定理可知,n=∏ki=1pcii,其中pi为互不相同的素数。
而因式分解是指,将一个合数分解为两个非平凡因子的乘积,比如6=2⋅3。
如何对合数n进行因式分解呢?
最简单的方式是枚举它可能的因子,由于n的最小因子一定在2到√n之间,因此可以在时间复杂度O(√n)内完成。
Pollard's Rho算法在1975年由John Pollard发明,对于分解合数n,它的期望时间复杂度仅为O(n14)。
首先我们考虑建立一组序列X=(x1,x2,…),其中xi+1=x2i+c(modn)。由于是处于n的剩余类环中,因此序列中实际包含的不同值最多为n个。序列的图像应该如下:
由于图像酷似一个ρ,因此算法得名rho。
由生日悖论可知,在一年有d天的情况下,当班级里有√d个学生的时候,学生中期望至少有一对学生的生日相同,即发生碰撞。将生日悖论用在我们的序列中,可知我们序列中的不同值的数量约莫为O(√n)。
设m为n的最小非平凡因子,易知m≤√n。我们记yi=xi(modm),可以推导:
yi+1=xi+1(modm)=(x2i+c(modn))(modm)=x2i+c(modm)=(xi(modm))2+c(modm)=y2i+c(modm)因此我们得到了新的序列Y=(y1,y2,…),同样根据生日悖论,可知序列中不同值的数量约为O(√m)≤O(4√n)。
由于第一次碰撞等概率发生在序列上,因此序列X中的环的入口期望为第√n/2个元素,而Y中的环的入口期望为第√m/2个元素。因此X中环的长度Cx期望为√n/2,同理Y中的环的长度Cy期望为√m/2。由于环的大小期望不同,那么就应该存在一对数i,j,满足xi≠xj∧yi=yj。这意味着n∤|xi−xj|∧m∣|xi−xj|。因此我们可以通过gcd(n,|xi−xj|)获得一个n的非平凡因子。
那么我们该如何获得i和j呢。有两种方法,一个是Floyd判圈算法,另外一种是倍增法。当Floyd判圈法记录两个下标i,j,每次循环i=i+1,j=j+2。当Cy∣(j−i)时,我们就能找到对应的i和j。而倍增法则通过枚举可能的Y序列中环上的某个值和环的长度上限实现,首先猜想y1落在环上且环的长度不大于1,如果第i次猜测失败,下一次就猜测y2i落在环上且环的长度不大于2i。两种算法都可以在O(√m)次循环内完成。
如果你挑选的x1和c不好的话,可能会导致最终没法找到n的任意因子。那么你可以在合适的时机,用新的随机数替代x1和c重新进行启发式查找。
注意,这个算法是建立在n是合数的前提下的,如果n是素数,那么这个算法就永远无法找到因子而陷入死循环中。所以在执行Pollard's Rho算法之前,需要先测试n是否是合数,这里可以采用随机算法Miller Rabin进行,这里不过多讲。
并且这里O(√m)是指的是循环次数,而不是时间复杂度,由于每次循环我们都会调用gcd,因此真实的时间复杂度为O(√mlog2n)。
int pollard_rho(int x, int c, int n)
{
int xi = x;
int xj = x;
int j = 2;
int i = 1;
while(i < n)
{
i++;
xi = (xi * xi + c) % n;
int g = gcd(n, abs(xi - xj));
if(g != 1 && g != n)
{
return g;
}
if(i == j)
{
j = j << 1;
xj = xi;
}
}
return -1;
}
上面的是倍增法的代码。