C++Pollard_rho分解質因數及其例題—————Prime Test

前言:

在觀看此博客之前請學習miller_rabin。

我們在分解質因子時也許只會用試根法(也就是暴力)。

而在此我們將學習一個玄學的算法——Pollard_rho。

概念:

Pollard_rho是一種基於隨機的算法,它的思路是先用miller_rabin來判斷當前數是否已經是素數了,如果是的話記錄並返回。如果不是,我們設要分解的數爲n,那麼我們考慮去找一個當前數的因數p,找到之後再分別對p和n/p分解質因數,有一點類似分治但是不是分治。

算法如圖:

那麼,我們要如何快速地找到當前數的因數呢?

這裏,我們採用以下方法:先隨機生成兩個1~n的數,利用這兩個數差的絕對值和n比較,來判斷這兩個數差的絕對值是不是n的一個因數。

可這又是爲什麼?

那麼我們就要講到一個叫生日悖論的東西了。

       這裏又出現了一個問題:我們爲什麼要使用兩個隨機數差的絕對值來判斷是否爲n的因數之一,而不是直接生成一個隨機數來進行判斷呢?

這裏我們來講一下生日悖論:

        生日悖論,指如果一個房間裏有23個或23個以上的人,那麼至少有兩個人的生日相同的概率要大於50%。這就意味着在一個典型的標準小學班級(30人)中,存在兩人生日相同的可能性更高。對於60或者更多的人,這種概率要大於99%。

如何計算:

第一個人的生日是 365選365

第二個人的生日是 365選364

第三個人的生日是 365選363

第n個人的生日是 365選365-(n-1)

那麼,所有人的生日都不同的概率爲:

所有人中,至少有兩個人生日相同的概率爲:

當n=23時,這個概率爲0.507,n=100時,概率爲0.999999692751072。

當然,選取兩個數也是有道理的。

一開始,我們先隨機生成兩個數x和c,每次都使x=x*x+c(mod n) ,另外再設置一個數y,y的初始值爲x,每進行2^k次操作,就讓y記爲現在的x。在計算的過程中,我們可以發現x=x*x+c(mod n)這個過程是會出現循環的,具體圖像會像希臘字母ρ(rho)一旦出現循環我們還沒有找到可以分解的因數,就意味着這次隨機失敗了,需要我們再次進行隨機。我們爲了記錄什麼時候出現了循環,我們每個2^k次計算就把y記錄成現在的x,如果在接下來的2^k次操作中,出現了y=x,就意味着出現了循環。所以,y的作用一是爲了當隨機的第二個數,二是爲了判斷循環。 

代碼如圖:

ll Pollard_rho(ll n1,ll c)//ll 爲long long
{
	ll i=1,k=2;
	ll x=rand()%n1;
	ll y=x;
	while(1)
	{
		i++;
		x=(qkch(x,x,n1)+c)%n1;
		ll d=gcd(y-x,n1);
		if(d>1&&d<n)
			return d;
		if(x==y)
			return n1;
		if(i==k)
		{
			y=x;
			k=k*2;
		}
	}
}

題目描述:

Given a big integer number, you are required to find out whether it's a prime number.

輸入:

The first line contains the number of test cases T (1 <= T <= 20 ), then the following T lines each contains an integer number N (2 <= N < 2 54).

輸出:

For each test case, if N is a prime number, output a line containing the word "Prime", otherwise, output a line containing the smallest prime factor of N.

樣例輸入:

2
5
10

樣例輸出:

Prime
2

此題爲模板題就不需多講。

代碼實現:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<ctime>
#define INF 1ll << 60
using namespace std;
#define ll long long
ll n,ans,t;
ll qkch(ll x,ll y,ll mod)
{
	ll res=0;
	while(y)
	{
		if(y&1)
			res=(res+x)%mod;
		x=(x*2)%mod;
		y/=2;
	}
	return res;
}
ll gcd(ll a,ll b)
{
    if(a==0)
		return 1;
	if(a<0)
		return gcd(-a,b);
	while(b>0)
	{
		ll t=a%b;
		a=b;
		b=t;
	}
	return a;
}
ll qkpow(ll x,ll y,ll mod)
{
	ll ans=1;
	while(y)
	{
		if(y&1)
			ans=qkch(ans,x,mod);
		x=qkch(x,x,mod);
		y/=2;
	}
	return ans;
}
bool miller_robin(ll n1)
{
	if(n1==2)
		return 1;
	if(n1<2||!(n1&1))
		return 0;
	ll x,u,pre;
	ll k=0;
	u=n1-1;
	while(!(u&1))
	{
		k++;
		u>>=1;
	}
	for(int i=1;i<=3;i++)
	{
		x=rand()%(n1-1)+1;
		x=qkpow(x,u,n1);
		pre=x;
		for(int j=1;j<=k;j++)
		{
			x=qkch(x,x,n1);
			if(x==1&&pre!=n1-1&&pre!=1)
				return 0;
			pre=x;
		}
		if(x!=1)
            return 0;
	}
	 return 1;
}
ll Pollard_rho(ll n1,ll c)
{
	ll i=1,k=2;
	ll x=rand()%n1;
	ll y=x;
	while(1)
	{
		i++;
		x=(qkch(x,x,n1)+c)%n1;
		ll d=gcd(y-x,n1);
		if(d>1&&d<n)
			return d;
		if(x==y)
			return n1;
		if(i==k)
		{
			y=x;
			k=k*2;
		}
	}
}
void findit(ll p)
{
	if(miller_robin(p)==1)
	{
		if (ans>p)
            ans=p;
		return;
	}
	ll p1=p;
	while(p1>=p)
	{
		p1=Pollard_rho(p1,rand()%(p-1)+1);
		//printf("%lld %lld\n",p,p1);
	}
	findit(p1);
	findit(p/p1);
}
int main()
{
	srand(time(0));
	scanf("%lld",&t);
	while(t--)
	{
		ans=INF;
		scanf("%lld",&n);
		if(miller_robin(n))
			printf("Prime\n");
		else
        {
            findit(n);
			printf("%lld\n",ans);
        }
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章