Pollard-Rho

算法简介

Pollard-Rho 主要用于质因数分解,是一个随机化算法。

前置算法

Miller-Rabin

伪随机函数

此算法用 \(f_x=f_{x-1}^2+c \bmod n\) 生成随机数用于寻找质因数。
(其中,\(f_1\)\(c\) 可以随机生成, \(n\) 为正在分解的数)
但是,这个函数会产生环,所以叫伪随机函数。

判环

考虑用两个指针,一个每次走一步,另一个每次走两步。
如果两个指针的值在某一时刻相等,则找到了环。
因为两个指针位置的差每次增加1,所以不用担心找不到环。

算法主体

如果当前的数是质数,记录一下,直接返回。
判质数可用 Miller-Rabin 实现。
用上面的伪随机函数生成 \(f\) 数组。
根据生日悖论,随机两个数 \(x\)\(y\) ,使得 \(\gcd(|x-y|,n)>1\) 的概率比只随机一个数的概率要大。
所以,用判环使用的两个指针的值作为 \(x\)\(y\)
\(d=\gcd(|x-y|,n)\)
\(d>1\)\(d<n\),则递归处理 \(d\)\(\frac{n}{d}\) 两个部分。
同时,如果找到了环,且还没有找到合法的因数,就重新随机 \(f_1\)\(c\)

现在的代码

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#define int long long
#define LD long double
#define ull unsigned long long
using namespace std;
int T,n,ans;
int MUL(int a,int b,int p) //a*b%p
{
	int x=(LD)a/p*b;
	return ((ull)a*b-(ull)x*p+p)%p;
}
int POW(int a,int b,int p) //a^b%p
{
	if(!b) return 1;
	if(b==1) return a;
	int sum=POW(a,b/2,p);
	if(b%2) return MUL(MUL(sum,sum,p),a,p);
	return MUL(sum,sum,p);
}
int MAX(int x,int y)
{
	return x>y?x:y;
}
int ABS(int x)
{
	return x>0?x:-x;
}
int gcd(int x,int y)
{
	if(!y) return x;
	return gcd(y,x%y);
}
int f(int x,int c,int p)
{
	return (MUL(x,x,p)+c)%p;
}
bool MR(int x)
{
	if(x==0||x==1) return false;
	if(x==2) return true;
	if(x%2==0) return false;
	int p=x-1,q=0;
	while(p%2==0) q++,p/=2;
	for(int i=1;i<=10;i++)
	{
		int a=rand()%(x-1)+1;
		if(POW(a,x-1,x)!=1) return false;
		int lst=1;
		for(int j=0;j<q;j++)
		{
			int t=POW(a,(1ll<<j)*p,x);
			if(t==1&&lst!=1&&lst!=x-1) return false;
			lst=t;
		}
		if(lst!=1&&lst!=x-1) return false;
	}
	return true;
}
int find(int x)
{
	if(x%2==0) return 2;
	if(MR(x)) return x;
	int t=rand()%(x+1);
	int a=t,b=t;
	int c=rand()%(x+1);
	while(1)
	{
		a=f(a,c,x),b=f(f(b,c,x),c,x);
		int d=gcd(ABS(a-b),x);
		if(d>1&&d<x) return d;
		if(a==b) return find(x);
	}
}
void PR(int x)
{
	if(x<=1) return;
	if(MR(x))
	{
		ans=MAX(ans,x);
		return;
	}
	int y=find(x);
	PR(y),PR(x/y);
}
signed main()
{
	srand(time(0));
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld",&n);
		if(MR(n)) puts("Prime");
		else
		{
			ans=0,PR(n);
			printf("%lld\n",ans);
		}
	}
	return 0;
}

黑科技 gcd

上面的代码因为效率过慢过不了洛谷上的 模板题
观察到代码中会使用很多次 gcd 函数,所以考虑优化计算 gcd 的方法。

新算法
设现在要计算 \(a\)\(b\) 两数的 gcd。
\(a\)\(b\) 都是偶数,则递归计算 \(2 \times gcd(\frac{a}{2},\frac{b}{2})\)
\(a\) 是偶数,且 \(b\) 是奇数,则递归计算 \(gcd(\frac{a}{2},b)\)
\(a\) 是奇数,\(b\) 是偶数同理。
\(a\)\(b\) 都是奇数,则递归计算 \(gcd(|a-b|,a)\)
证明:
设原来 \(a\)\(b\) 的 gcd 为 \(k\)
\(a=kA\)\(b=kB\)
\(|a-b|=k|A-B|\)
因为 \(A\)\(B\) 必然互质,
所以,现在要证明的就是 \(A\)\(|A-B|\) 互质。
为了方便,先假设 \(A>B\) ,去掉绝对值。相反的情况的证明也是类似的。
考虑反证法。
\(A\)\(A-B\) 不互质,
\(A=qx\)\(A-B=qy\)
\(B=A-(A-B)=qx-qy=q(x-y)\) ,所以 \(A\)\(B\) 不互质,不成立。
所以前面的递归是成立的。

把原算法中的 gcd 部分换成这种写法,并加上一些比较常见的卡常技巧即可轻松通过本题。
贴一个用迭代实现的板子。

//__builtin_ctzll(x) 函数用于求x在二进制中末尾0的个数
int gcd(int x,int y)
{
	if(!x) return y;
	if(!y) return x;
	int t=__builtin_ctzll(x|y);
	x>>=__builtin_ctzll(x);
	do
	{
		y>>=__builtin_ctzll(y);
		if(x>y) swap(x,y);
		y-=x;
	}while(y);
	return x<<t;
}
posted @ 2020-12-03 10:17  BBD186587  阅读(472)  评论(0编辑  收藏  举报