Pollard Rho算法分解因数

分解因数算法


给定一个正整数N,求其所有质因数

朴素算法

最贴近人的想法,就是从2开始一直到 N \sqrt{N} N ,用N取除以这些数,只要是能够整除就是因数,然后判定该因数是否是素因数

for(int i=2;i<=sqrt(N);i++){
	if(N%i==0){
		if(isPrime(i)){
			//i是N的素因数
		}
	}
}

唯一分解定理优化

任何正整数N都可以分解为若干个素数的乘积
N = p 1 k 1 p 2 k 2 . . . p n k n N=p_1^{k_1}p_2^{k_2}...p_n^{k_n} N=p1k1p2k2...pnkn
比如 48 = 2 4 × 3 1 48=2^4\times 3^1 48=24×31

对于刚才纯朴素的做法,我们在找到 2 2 2是48的素因数之后,如果不加处理后来又会发现4是48的因数但不是素因数,

可以将 2 k 2^k 2k从N的所有因数中去掉

int temp=N;
for(int i=2;i<=sqrt(temp);++i){
	if(temp%i==0){
		//i是temp的素因数,之所以没有判断素性就直接说是素因数,是因为有下面这个while保证
		while(temp%i==0){//实际上相当于筛子,筛去了i^k因子
			temp/=i;//由于temp%i=0因此i整除temp,此举将temp中所有i因数即其幂去掉
		}
	}
}
if(temp!=1){
	//最后剩下的temp也是N的素因数
}

只判断 t e m p temp%i==0 temp貌似只能说明 i i i t e m p temp temp的因数,为什么还能将得到更严格结论" i i i t e m p temp temp的素因数"呢?

假设 i i i t e m p temp temp的合因数

根据唯一分解定理, i = p 1 p 2 . . . p n , p i 为 素 数 i=p_1p_2...p_n,p_i为素数 i=p1p2...pn,pi

显然对于一个合数,i至少有两个素因数,不妨设为 1 < p 1 , p 2 < i 1<p_1,p_2<i 1<p1,p2<i

p 1 ∣ i , i ∣ t e m p ⇒ p 1 ∣ t e m p p_1|i,i|temp\Rightarrow p_1|temp p1i,itempp1temp

那么 p 1 p_1 p1 t e m p temp temp的比i小的素因数

现在将时光回溯 i = p 1 i=p_1 i=p1时,我们的while循环做了一件事:

		while(temp%p1==0){
			temp/=p1;
		}

此举保证了出while循环的时候,temp不再含有 p 1 p1 p1因子,也就是 p 1 ∤ t e m p p1 \nmid temp p1temp

现在时光前进到 i = t e m p i=temp i=temp时,我们刚刚得到结论 p 1 ∣ t e m p p_1| temp p1temp

于是产生了矛盾

因此假设不成立,即 i i i t e m p temp temp的素因数

Pollard Rho算法

ρ \rho ρ

算法思想

朴素算法是从 [ 2 , N ] [2,\sqrt{N}] [2,N ]里面遍历所有数找N的因数

而现在我们不轮着找,而是挑着找,就类似于抽样检测.

用随便挑的两个数的差去找,啥意思呢?

∀ x 1 , x 2 ∈ [ 2 , N ] \forall x_1,x_2\in [2,\sqrt{N}] x1,x2[2,N ],我们求 ∣ x 2 − x 1 ∣ |x_2-x_1| x2x1,然后用这个数去试是否是 N N N的因数

为啥不直接选一个 ∀ x ∈ [ 2 , N ] \forall x\in[2,\sqrt{N}] x[2,N ],然后用这个x去比划N,而是选两个x的差去比划呢?

0.生日悖论

这个只能用概率解释了,生日悖论问题

1.考虑从 [ 1 , 1000 ] [1,1000] [1,1000]上选k个数,求任意两个数的差不等于x的概率

2.考虑一伙子k个人,任意两个人生日不同的概率(生日悖论)

上面那个我不会求,下面这个我会

假设一年有n天,求k个人生日都不相同的概率

第一个人的生日可以在n天中的任意一天,有n种情况

第二个人的生日不能和第一个人相同,有n-1种情况

第三个人的生日不能和前两个人相同,有n-2种情况

第k个人的生日不能和前k-1个人相同,有n-(k-1)种情况

因此k个人生日都不相同的概率为
P = n ( n − 1 ) ( n − 2 ) . . . ( n − ( k − 1 ) ) n k = n n × n − 1 n × n − 2 n × . . . × n − k + 1 n = 1 × ( 1 − 1 n ) × ( 1 − 2 n ) × . . . × ( 1 − k − 1 n ) P=\frac{n(n-1)(n-2)...(n-(k-1))}{n^k}\\ =\frac{n}{n}\times \frac{n-1}{n}\times \frac{n-2}{n}\times ...\times \frac{n-k+1}{n}\\ =1\times (1-\frac{1}{n})\times (1-\frac{2}{n})\times ...\times (1-\frac{k-1}{n}) P=nkn(n1)(n2)...(n(k1))=nn×nn1×nn2×...×nnk+1=1×(1n1)×(1n2)×...×(1nk1)
这个数可不好求,但是可以放缩,用到的是高中就见过的老伙计了 e x ≥ x + 1 e^x\ge x+1 exx+1
1 − i n ≤ e − i n 1-\frac{i}{n}\le e^{-\frac{i}{n}} 1nieni
因此
P = 1 × ( 1 − 1 n ) × ( 1 − 2 n ) × . . . × ( 1 − k − 1 n ) ≤ e − 1 n × e − 2 n × . . . × e − k − 1 n = e − 1 n ∑ i = 1 k − 1 i = e − k ( k − 1 ) 2 n P=1\times (1-\frac{1}{n})\times (1-\frac{2}{n})\times ...\times (1-\frac{k-1}{n})\\ \le e^{-\frac{1}{n}}\times e^{-\frac{2}{n}}\times ...\times e^{-\frac{k-1}{n}}\\ =e^{-\frac{1}{n}\sum_{i=1}^{k-1} i}\\ =e^{-\frac{k(k-1)}{2n}} P=1×(1n1)×(1n2)×...×(1nk1)en1×en2×...×enk1=en1i=1k1i=e2nk(k1)
即k个人生日都不同的概率小于等于 e − k ( k − 1 ) 2 n e^{-\frac{k(k-1)}{2n}} e2nk(k1)

那么存在两个人生日相同的概率为 1 − e − k ( k − 1 ) 2 n 1-e^{-\frac{k(k-1)}{2n}} 1e2nk(k1)

忙活半天求这个概率的缩放值有啥目的呢?

为了证明当人稍微多一点,存在两个人生日同一天的概率就会变大,并且是超出直觉的大

比如n=365,求存在两个人生日相同的概率达到50%,至少需要几个人
1 − e − k ( k − 1 ) 730 ≥ 1 2 1-e^{-\frac{k(k-1)}{730}}\ge \frac{1}{2}\\ 1e730k(k1)21

k ( k − 1 ) ≥ 730 ln ⁡ 2 = 505.99744... ≈ 506 k(k-1)\ge 730\ln 2=505.99744...\approx 506 k(k1)730ln2=505.99744...506

23 ∗ 22 = 506 23*22=506 2322=506

因此 k ≥ 23 k\ge 23 k23

即至少23个人就可以

想想这个事情,随便路上找一个人比较生日,相同的概率只有1/365

但是随便找22个人都问一遍,就会有一半的概率,其中至少有一个人和我同生日

放在这里,求因数,怎么操作呢?

我们要产生一系列 [ 2 , N ] [2,\sqrt{N}] [2,N ]上的随机数,然后每次取一对做差检验这个差是不是N的因数

1.如何产生随机数

构造随机数函数 f ( x ) = ( x 2 + c ) m o d    n f(x)=(x^2+c)\mod n f(x)=(x2+c)modn,作用是生成随机数序列,其中c是随便指定的一个数,然后随便选取一个正整数 x 0 x_0 x0作为起点
x 1 = f ( x 0 ) = ( x 0 2 + c ) m o d    n x 2 = f ( x 1 ) = ( x 1 2 + c ) m o d    n . . . x m = f ( x m − 1 ) = ( x m − 1 2 + c ) m o d    n x_1=f(x_0)=(x_0^2+c)\mod n\\ x_2=f(x_1)=(x_1^2+c)\mod n\\ ...\\ x_m=f(x_{m-1})=(x_{m-1}^2+c)\mod n x1=f(x0)=(x02+c)modnx2=f(x1)=(x12+c)modn...xm=f(xm1)=(xm12+c)modn
计算若干次后一定会陷入循环

为什么一定会陷入循环?

假设不会陷入循环,

则每一次计算都会得到一个与前面所有 x i x_i xi都不同的x值,但是 ∀ x i ∈ [ 0 , n − 1 ] \forall x_i\in[0,n-1] xi[0,n1],即 x i x_i xi顶多有n种取值,那么至多计算n次之后,由抽屉原理,一定会又两个 x x x值相同.

假设是 x j = f ( f ( f ( f ( . . . f ( x i ) ) ) ) ) x_j=f(f(f(f(...f(x_i))))) xj=f(f(f(f(...f(xi))))) x i x_i xi经过若干次迭代计算之后得到 x j x_j xj,并且 x i = x j x_i=x_j xi=xj

那么再计算 x j + 1 = f ( x j ) = f ( x i ) = x i + 1 x_{j+1}=f(x_j)=f(x_i)=x_{i+1} xj+1=f(xj)=f(xi)=xi+1

以此类推可以得到 x j + k = x i + k x_{j+k}=x_{i+k} xj+k=xi+k

显然陷入了循环

2.如何检查已经陷入了循环?

利用链表上快慢指针检查是否有环的思想

一个400米的圆形跑道,

两个傻子不知道跑道是⭕形状的,只知道沿着跑到一直跑

两个傻子同时同地起跑,快傻子每秒一米,慢傻子每秒半米,

相对速度差为每秒半米,那么经过800秒,相对路程差为 800 × 0.5 = 400 m 800\times 0.5=400m 800×0.5=400m,

快傻子正好比慢傻子快一圈,两人再次相遇表明"地球是圆的"即跑道是套圈的

在这里令 x i = f ( f ( x i − 1 ) ) , x j = f ( x j − 1 ) x_i=f(f(x_{i-1})),x_j=f(x_{j-1}) xi=f(f(xi1)),xj=f(xj1), x i x_i xi就是那个快傻子, x j x_j xj就是那个慢傻子,他俩就是那个海尔兄弟,舒克贝塔

并且两个傻子从并排起跑到再次相遇,他们的相对路程经理了一个从0到400的过程,遍历了 [ 0 , 400 ] [0,400] [0,400]间的所有值

那么在这里两个快慢指针的差也就遍历了 f ( x ) f(x) f(x)函数能够产生的,在圈上的,所有随机数的差

为啥说"在圈上的"

这里陷入循环不一定是从 x 0 x_0 x0就在循环中,

可能是中间某个状态才进入循环

就类似于一个 ρ \rho ρ,一开始的 x x x值是在 ρ \rho ρ的"腿"上的,后来的 x x x才进入 ρ \rho ρ的圈上

这个方法叫做"Floyd判环"

当两个快慢指针再次相遇的时候,说明所有 f ( x ) f(x) f(x)能够产生的圈上的随机数的差都不是 N N N的因数

那么只能说明选的随机数函数真的太逊了

然后对 f ( x ) = x 2 + c f(x)=x^2+c f(x)=x2+c进行修改比如修改一个初始值 x 0 ′ x_0' x0或者修改 c c c或者直接修改函数表达式 f ( x ) = x 2 + x + c f(x)=x^2+x+c f(x)=x2+x+c等等

3.最大公因数优化

刚才我们只是检查 ∣ x i − x j ∣ |x_i-x_j| xixj能不能整除 N N N,即检查 ∣ x i − x j ∣ |x_i-x_j| xixj是不是 N N N的因数

但是由于 g c d ( ∣ x i − x j ∣ , N ) ∣ N   a n d   g c d ( ∣ x i − x j ∣ , N ) ∣ ∣ x i − x j ∣ gcd(|x_i-x_j|,N)|N\ and\ gcd(|x_i-x_j|,N)||x_i-x_j| gcd(xixj,N)N and gcd(xixj,N)xixj,那么只要 ∣ x i − x j ∣ |x_i-x_j| xixj是N的因数,那么 g c d ( ∣ x i − x j ∣ , N ) gcd(|x_i-x_j|,N) gcd(xixj,N)也是N的因数

并且用最大公因数去和N比划的机会只会比只用 ∣ x i − x j ∣ |x_i-x_j| xixj去直接比划N更多

对于 ∣ x i − x j ∣ |x_i-x_j| xixj可以要求放松一点,只要是 ∣ x i − x j ∣ |x_i-x_j| xixj的任何非1真因数能够整除 N N N就算是给N找到了一个因数

完整代码

#include <iostream>
#include <algorithm>
using namespace std;


long long gcd(const long long &a, const long long &b) {
	if (b == 0)
		return a;
	return gcd(b, a % b);
}

long long f(const long long &x0, const long long &c, const long long &mod) {
	return (x0 * x0 % mod + c) % mod;
}

long long Pollard_Rho(const long long &N) {
	long long c = rand() % (N - 1) + 1; //c∈[1,N-1]
	long long slow = f(0, c, N);
	long long fast = f(f(0, c, N), c, N);
	while (slow != fast) {
		long long d = gcd(abs(slow - fast), N);
		if (d > 1)
			return d;//找到N的真因数d,返回
		slow = f(slow, c, N);
		fast = f(f(fast, c, N), c, N);
	}
	return N;//失败
}
long long N;
int time = 0;

int main() {

	while (cin >> N) {
		cout << ++time << ":";
		cout << Pollard_Rho(N) << endl;
	}
}

随便给几个数,这个程序的运行结果是怎样的呢?

8
1:8
8
2:4
8
3:8
8
4:4
8
5:8
8
6:8
9
7:9
9
8:9
9
9:9
9
10:3

可以发现,当N=8的时候,第一次失败了,第二次成功找到4,第三次又失败了,第四次又找到4,第五六次都失败了

当N=9,第九次失败,第十次找到三

如果真的失败了,多选上几个c去世或者改一下f函数去世.

如果试了很多次都失败了,就可以从概率上极大似然地认为N没有真因子了

BUUCTF-Alice和Bob

分解质因数 98554799767 98554799767 98554799767

还是运行刚才的程序

98554799767
1:98554799767
98554799767
2:101999

第二次就找到了真因子101999,我们只能确定他是 98554799767 98554799767 98554799767的因子,但是 101999 101999 101999是不是素数还没有检查

但是吧 101999 101999 101999用2,3,5,13,17这一些除一下都除不开,长成这样很像一个素数, 98554799767 / 101999 = 966233 98554799767/101999=966233 98554799767/101999=966233,直接提交101999和966233就通过了

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰球球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值