pollard_rho学习笔记 数论

本文介绍了一种高效的质因数分解算法——Pollard Rho算法,该算法利用生日悖论原理随机生成数对以提高找到因数的概率,并采用分治思想进一步分解找到的因数。

pollard_rho算法是用来解决质因数分解问题的。
我们知道,朴素的质因数分解是O(n)O(\sqrt{n})O(n)的,但是如果nnn很大的话应该怎么办呢?这时候就要用到pollard_rho了。
pollard_rho也是一种基于随机的算法,它的思路是先用miller_rabin来判断当前数是否已经是素数了,如果是的话记录并返回。如果不是,我们设要分解的数为nnn,那么我们考虑去找一个当前数的因数ppp,找到之后再分别对pppn/pn/pn/p分解质因数,有一点类似分治但是不是分治。
那么我们现在的问题是如何快速寻找一个数的一个因数?这个算法用到了生日悖论,不了解的话可以自行搜索一下,我这里不对其赘述。根据生日悖论,我们引申出如果我们随机生成一个111nnn的数,它是要分解数nnn的因数的概率很小,但是如果我们随机生成两个111nnn的数,它们的差的绝对值是nnn的概率就会变大。我们基于这种原理来找nnn的因子。
对于我们这两个数,我们不能每次都随机生成。我们是这样得来每次的两个数的,一开始,先随机生成一个xxx和一个ccc,每次让x=x∗x+c(mod n)x=x*x+c(mod\ n)x=xx+c(mod n),另一个数yyy一开始的值是xxx,每进行2k2^k2k次让yyy记为现在的xxx。我们发现,x=x∗x+c(mod n)x=x*x+c(mod\ n)x=xx+c(mod n)这个过程是会出现循环的,具体画出来会像一个希腊字母ρ(rho)。一旦出现循环我们还是没有找到可以分解的因数就说明这次随机失败了。我们为了记录什么时候出现了循环,每隔2k2^k2k把y记录成当前xxx,如果在接下来的2k2^k2k次操作中xxxyyy相等了,就意味着出现了循环。所以yyy的作用一来是当那随机的第二个数,二来是记录是否出现循环。
最后来分析一下复杂度(某位神犇给本蒻讲的):首先因为这是个随机算法,所以它只有期望复杂度。根据生日悖论,我们分解出每个质因数期望随机出质因数大小\sqrt{质因数大小}次即可,而最坏情况最小质因数大小是要分解的数\sqrt{要分解的数}的,所以最后复杂度应该是O(n14)O(n^{\frac{1}{4}})O(n41)的,nnn是要分解的数。实际上,应该在质因数越多的时候质因数大小越小,所以会跑得快,所以并不需要在复杂度上乘期望质因子个数,这个的影响应该是个常数级别的。
最后附一下代码:

#include <iostream>
#include <ctime>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstdlib>
using namespace std;

int t,s=20,cnt;
long long fac[1001];
long long ksc(long long x,long long y,long long mod)
{
	long long res=0;
	while(y)
	{
		if(y&1)
		res=(res+x)%mod;
		x=(x<<1)%mod;
		y>>=1;
	}
	return res;
}
long long ksm(long long x,long long y,long long mod)
{
	long long res=1;
	while(y)
	{
		if(y&1)
		res=ksc(res,x,mod);
		x=ksc(x,x,mod);
		y>>=1;
	}
	return res;
}
int miller_rabin(long long n)
{
	if(n==2)
	return 1;
	if(n<2||!(n%2))
	return 0;
	long long u,pre,x;
	int k=0;
	u=n-1;
	while(!(u&1))
	{
		++k;
		u>>=1;
	}
	for(int i=1;i<=s;++i)
	{
		x=rand()%(n-2)+2;
		x=ksm(x,u,n);
		pre=x;
		for(int j=1;j<=k;++j)
		{
			x=ksc(x,x,n);
			if(x==1&&pre!=1&&pre!=n-1)
			return 0;
			pre=x;
		}
		if(x!=1)
		return 0;
	}
	return 1;
}
long long gcd(long long a,long long b)//注意与一般的gcd不一样
{
    if (a==0) return 1;//pollard_rho的需要
    if (a<0) return gcd(-a,b);//可能有负数
    while (b){
        long long t=a%b; a=b; b=t;
    }
    return a;
}
long long pollard_rho(long long n,long long c)//找因子
{
	long long i=1,k=2;//用来判断是否形成了环
	long long xx=rand()%n,y=xx;
	while(1)
	{
		i++;
		xx=(ksc(xx,xx,n)+c)%n;
		long long d=gcd(y-xx,n);
		if(1<d&&d<n)//找到一个因数
		return d;
		if(y==xx)//出现循环,那么这次寻找失败
		return n;
		if(i==k)//相当于每次找连续k这么多次取模有没有得到相同余数
		{
			y=xx;
			k<<=1;
		} 
	} 
}
void find(long long n)//通过找因数来找质因子
{
	if(miller_rabin(n))
	{
		fac[++cnt]=n;//记录质因子
		return;
	}
	long long p=n;
	while(p>=n)
	p=pollard_rho(p,rand()%(n-1)+1);//如果转了一圈还是p,则继续while循环
	//p是当前找到的一个因数(不一定是质因数),再分别找p和n/p的质因数
	find(p);
	find(n/p);
}
int main()
{
	srand(time(0)+19260817);
	scanf("%d",&t);
	while(t--)
	{
		long long x;
		scanf("%lld",&x);
		if(miller_rabin(x))
		{
			printf("Prime\n");
			continue;
		}
		cnt=0;	
		find(x); 
		sort(fac+1,fac+cnt+1);
		printf("%lld\n",fac[1]);
	}
	return 0;
}

题单:
洛谷1075质因数分解
HDU3864 D_num
洛谷4358密钥破解

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值