Pollard‘s rho大数分解算法

本文解析了Pollard's rho算法如何通过构造递推数列和模n的周期性来寻找素因子。通过将问题转化为生日问题并利用随机性和期望值,算法巧妙地检测合数因子。实践案例和代码展示了如何在HDU-1488 Flavius Josephus Reloaded问题中应用该算法。

目录

一,问题

二,Pollard's rho算法思路

1,构造递推数列

2,生成mod n的递推数列

3,近似生日问题

4,Pollard's rho算法思路

5,时间复杂度

6,实现

7,测试

三,OJ实战

HDU - 1488 Flavius Josephus Reloaded


一,问题

给定一个很大的整数n,求出n的一个素因子

PS:如果求不出非平凡因子,也能说明n是素数

二,Pollard's rho算法思路

1,构造递推数列

构造一个一阶至少二次的递推式,如a_{k+1}=a_k^2+c

2,生成mod n的递推数列

随便取初始值,根据a_{k+1}\equiv a_k^2+c(mod\,n)得到一个数列,那么数列的每一项都在[0,n-1]的范围内

显然,数列一定会陷入循环。

如n=30,数列{0,1,2,5,26,17,20,11,2...}

这个数列mod 5是{0,1,2,0...}

这个数列模3是{0,1,2,2...}

3,近似生日问题

生成数列是比较随机的,所以我们可以把它看做生日问题

根据生日问题_csuzhucong的博客-优快云博客中的计算,第一个重复出现的数的下标的期望,不超过(1+e^{-\frac{1}{2}})\sqrt k+2,其中k是可以取的数的数目

下文用2sqrt(k)表示(实际上应该是2.7sqrt(k)),不用太关注常数

4,Pollard's rho算法思路

如果n有非平凡因子p,那么大概率数列mod p出现周期会比mod n出现周期早,因为前者是2sqrt(p),后者是2sqrt(n)

按照判断链表是否有环的方法,我们依次检测a_i, a_{2i}是否相等,即是否出现周期

因为不知道p的值,所以我们用gcd(a_i-a_{2i},\, n)>1来判断进入了mod p的循环,这样,我们就找到了因子p

如果对于找到的第一个满足gcd(a_i-a_{2i},\, n)>1的i,有a_i=a_{2i},则n有可能是素数。

如果确定n是合数,可以多尝试一些c的值,总能找到p的,

如果不确定n是不是素数,多尝试一些c的值之后还是找不到因子,那n可能就是素数。

5,时间复杂度

平均情况下的时间复杂度是sqrt(p)*tc*tn,

其中tc表示尝试不同的c的次数,tc的期望应该非常小,感觉小于2,

其中tn表示单次调用gcd函数的运行时间,基本上可以理解为log n

所以,Pollard's rho算法的平均时间复杂度是O(n^{\frac{1}{4}} * log n)

6,实现

static long long gcd(long long a, long long b)
{
	if (b == 0)return a;
	return gcd(b, a % b);
}
int findDiv(long long n) //n*2不超过long long max
{
	int n4 = pow(n, 1.0 / 4) * 10;
	vector<int>vc{ 1,13,59,97,311,499,997,1201,2003,3533,7177,12799,19997,25111,47777,49999,1000000007 };
	vector<long long>v(n4);
	for (auto c : vc) {
		v[0] = rand()%n;
		for (int i = 1; i < v.size(); i++) {
			v[i] = (Multi::multiAdd(v[i - 1], 2, n) + c) % n;
			long long dif = abs(v[i] - v[i / 2]);
			if (dif && gcd(dif, n) > 1)
				return dif;
		}
		continue;
	}
	return 1;
}

理论上n4 = pow(n, 1.0 / 4) * 2.7就够了,但是这只是按照均值算,n4越大准确率越高。

7,测试

int main()
{
	for (long long pi = 10;; pi*=10) {
		for (long long n = pi;; n++) {
			if (findDiv(n) == 1) {
				cout << n << endl;
				break;
			}
		}
		if (pi == 1000000000000000000)break;
	}
	return 0;
}

从结果来看,准确率还不是特别高,素数肯定不会被当做合数,但是合数有可能被当成素数。

我觉得难点在于哈希部分,或许我这里用的哈希函数太简单了,导致有某种类似于“模素数的数据群集”现象。

三,OJ实战

HDU - 1488 Flavius Josephus Reloaded

Flavius Josephus once was trapped in a cave together with his comrade soldiers surrounded by Romans. All of Josephus' fellow soldiers preferred not to surrender but to commit suicide. So they all formed a circle and agreed on a number k. Every k-th person in the circle would then commit suicide. However, Josephus had different priorities and didn't want to die just yet. According to the legend he managed to find the safe spot in the circle where he would be the last one to commit suicide. He surrendered to the Romans and became a citizen of Rome a few years later.

It is a lesser known fact that the souls of Josephus and his comrades were all born again in modern times. Obviously Josephus and his reborn fellow soldiers wanted to avoid a similar fiasco in the future. Thus they asked a consulting company to work out a better decision scheme. The company came up with the following scheme:

1.For the sake of tradition all soldiers should stand in a circle. This way a number between 0 and N-1 is assigned to each soldier, where N is the number of soldiers.
2.As changing numbers in the old scheme turned out to be horribly inefficient, the number assigned to a soldier will not change throughout the game.
3.The consulting company will provide two numbers a and b which will be used to calculate the number of the next soldier as follows: Let x be the number of the current soldier, then the number of the next soldier is the remainder of a*x^2 + b mod N.
4.We start with the soldier with number 0 and each soldier calculates the number of the next soldier according to the formula above.
5.As everyone deserves a second chance a soldier will commit suicide once his number is calculated for the second time.
6.In the event that the number of a soldier is calculated for the third time the game will end and all remaining soldiers will surrender.

You are to write a program that given the number of soldiers N and the constants a and b determines the number of survivors.

Input

The input file consists of several test cases. Each test case consists of a single line containing the three integers N (2 ≤ N ≤ 10^9), a and b (0 ≤ a,b < N) separated by white space. You may safely assume that the first soldier dies after no more than one million (10^6) steps. The input is terminated by a single number 0 which should not be processed.

Output

For each test case output a single line containing the number of soldiers that survive.

Sample Input

2 1 1
5 1 1
10 3 7
101 9 2
698253463 1 181945480
1000000000 999999999 999999999
0

Sample Output

0
2
4
96
698177783
999999994

题意:

输入n,a,b,生成mod n的数列,初始项为0,后面每项为前一项的平方乘以a再加b

如果有个数出现了3次,那么就停止,对于出现了2次的数,对应的士兵杀掉,问还剩多少士兵?

分析:

这个其实并不是Pollard‘s rho算法,但是计算过程是差不多的,只是在这个数列中搜寻的目标不同而已。

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


int main()
{
    ios::sync_with_stdio(false);
    long long n,a,b;
    unordered_map<long long,int>m;
    while(cin>>n){
        if(n==0)break;
        cin>>a>>b;
        m.clear();
        long long x=0,ans=n;
        while(true) {
            if (m[x] == 1)ans--;
            m[x]++;
            if(m[x]==3)break;
            x = (x * x % n * a + b) % n;
        }
        cout<<ans<<endl;
    }
    return 0;
}

<think>我们详细解释 Pollard Rho 算法用于质因数分解的原理。该算法由 John Pollard 在 1975 年提出,主要用于分解大整数的非平凡因子。其核心思想是利用一个特定的伪随机序列来生成可能因子,并通过最大公约数(GCD)检测来寻找因子。 ### 1. 算法动机 直接试除法在分解大整数时效率低下(尤其是当因子较大时)。Pollard Rho 算法利用以下观察: - 设 $n$ 为待分解的合数,$p$ 是 $n$ 的一个非平凡因子($1<p<n$)。 - 考虑一个模 $n$ 的伪随机序列 $\{x_k\}$,如果序列模 $p$ 的值出现重复(即 $x_i \equiv x_j \pmod{p}$),那么 $|x_i - x_j|$ 将是 $p$ 的倍数。此时,计算 $\gcd(|x_i - x_j|, n)$ 可能会得到 $p$(或者 $n$ 本身,此时需调整参数重新尝试)。 然而,直接存储所有序列值并检查重复需要大量内存。Pollard Rho 算法通过 Floyd 的循环检测算法(龟兔赛跑)来高效检测循环,从而节省内存。 ### 2. 伪随机函数 通常使用一个二次函数生成伪随机序列: $$ x_{k+1} = (x_k^2 + c) \mod n $$ 其中 $c$ 是一个随机常数(通常 $c \neq 0, -2$),初始值 $x_0$ 随机选择。 ### 3. 循环检测(Floyd 方法) 设序列 $\{x_k\}$ 模 $p$ 存在循环(因为模 $p$ 的值域有限,循环必然出现)。Floyd 方法使用两个指针(龟和兔): - 龟指针每次走一步:$t = x_i$ - 兔指针每次走两步:$h = x_{2i}$ - 当 $i$ 增加到某个值时,若 $\gcd(|t - h|, n) > 1$,则找到了一个因子。 具体步骤: 1. 初始化:$x = y = 2$(通常从2开始),$c$ 随机选择(如 $c=1$),$d = 1$。 2. 循环直到 $d \neq 1$: - $x = f(x) = (x^2 + c) \mod n$ - $y = f(f(y))$ (即 $y$ 走两步) - 计算 $d = \gcd(|x - y|, n)$ 3. 如果 $d = n$,则算法失败(需换 $c$ 重新开始);否则 $d$ 是 $n$ 的一个因子。 ### 4. 为什么能分解? 由于序列模 $p$ 会形成循环(形如希腊字母 $\rho$,因此得名),而模 $n$ 的序列可以看作模 $p$ 和模 $n/p$ 的组合。当两个指针在模 $p$ 下进入循环时,$|x_i - x_j|$ 是 $p$ 的倍数,因此 $\gcd(|x_i - x_j|, n)$ 至少包含因子 $p$。但若同时是 $n$ 的倍数,则得到 $d=n$(失败)。 ### 5. 算法效率 - 期望时间复杂度为 $O(n^{1/4} \log n)$,比试除法($O(\sqrt{n})$)快得多。 - 算法是概率性的,可能失败,但可通过调整随机参数 $c$ 和 $x_0$ 重新尝试。 ### 6. 代码示例(Python) 以下是一个简化的 Pollard Rho 实现: ```python import math import random def pollard_rho(n): if n == 1: return 1 if n % 2 == 0: return 2 # 函数 f(x) = x^2 + c x = random.randint(2, n-1) y = x c = random.randint(1, n-1) d = 1 while d == 1: x = (x*x + c) % n y = (y*y + c) % n y = (y*y + c) % n # y 走两步 d = math.gcd(abs(x-y), n) if d == n: # 失败,重新选择参数 return pollard_rho(n) return d # 示例:分解 n = 10403 n = 10403 factor = pollard_rho(n) print(f"{n} 的一个因子是: {factor}") ``` ### 7. 完整质因数分解 使用 Pollard Rho 算法分解一个合数 $n$ 的完整步骤: 1. 如果 $n$ 是质数(用米勒-拉宾测试),直接输出。 2. 用 Pollard Rho 算法找到一个因子 $d$。 3. 递归分解 $d$ 和 $n/d$。 注意:实际应用中需处理小因子(如先试除小质数)以提升效率。 ### 总结 Pollard Rho 算法利用伪随机序列的循环特性和最大公约数计算,高效地寻找大整数的非平凡因子。其核心在于循环检测和随机参数调整,是一种概率算法但效率显著高于试除法[^1][^2]。 §§相关问题§§ 1. Pollard Rho 算法中如何选择随机参数 $c$ 和初始值 $x_0$ 以提高成功率? 2. 米勒-拉宾素性测试的原理和实现细节是什么? 3. 除了 Pollard Rho,还有哪些高效大数分解算法(如二次筛法、数域筛法)? 4. 如何将 Pollard Rho 算法与试除法结合进行完整的质因数分解?</think>### Pollard Rho 算法原理详解 Pollard Rho 算法是一种高效的**概率性质因数分解算法**,专门用于寻找大整数的非平凡因子(即大于1且小于整数本身的因子)。其核心思想基于**生日悖论**和**循环检测**,通过伪随机序列寻找因子。以下是逐步原理分析: --- #### 1. 算法基础:生日悖论 生日悖论指出:在 $N$ 个人中,有两人同一天生日的概率远高于直觉预期。类似地,在模 $p$($p$ 是 $n$ 的因子)下,随机序列中两个值碰撞($x_i \equiv x_j \pmod{p}$)的概率较高。 - 若 $x_i \equiv x_j \pmod{p}$,则 $\gcd(|x_i - x_j|, n)$ 可能是 $p$ 的倍数(即 $n$ 的非平凡因子)。 - 期望碰撞次数为 $O(\sqrt{p})$,远低于 $O(p)$,这是算法高效的关键[^1]。 --- #### 2. 伪随机序列生成 定义迭代函数生成序列: $$ f(x) = (x^2 + c) \mod n $$ 其中: - $x$ 是当前值(初始随机选择) - $c$ 是随机常数(通常 $c \neq 0, -2$) - 序列示例:$x_0, x_1 = f(x_0), x_2 = f(x_1), \dots$ 该序列在模 $n$ 下看似随机,但在模 $p$($p|n$)下会**形成循环**(形如希腊字母 $\rho$,算法因此得名)。 --- #### 3. Floyd 循环检测 为高效检测碰撞,使用两个指针(龟兔赛跑法): - **龟指针**($x$):每次移动一步,$x_{i+1} = f(x_i)$ - **兔指针**($y$):每次移动两步,$y_{i+1} = f(f(y_i))$ - 当 $\gcd(|x_i - y_i|, n) > 1$ 时,找到非平凡因子(可能为 $p$) ```python def pollard_rho(n): x = y = random.randint(2, n-1) # 随机初始化 c = random.randint(1, n-1) # 随机常数 d = 1 while d == 1: x = (x*x + c) % n # 龟走一步 y = (y*y + c) % n # 兔走第一步 y = (y*y + c) % n # 兔走第二步 d = math.gcd(abs(x - y), n) # 检测碰撞 return d # 返回因子 (可能是 n 本身) ``` --- #### 4. 算法步骤 1. **输入**:合数 $n$(非质数) 2. **初始化**: - 随机选择 $x, c \in [2, n-1]$ - 设 $y = x$, $d = 1$ 3. **迭代**: - $x \leftarrow f(x) = (x^2 + c) \mod n$ - $y \leftarrow f(f(y))$(移动两步) - $d \leftarrow \gcd(|x - y|, n)$ 4. **终止条件**: - 若 $d > 1$,返回 $d$(找到因子) - 若 $d = n$,失败(需换 $c$ 重试) 5. **递归分解**:对因子 $d$ 和 $n/d$ 递归调用算法 --- #### 5. 数学原理 设 $p$ 是 $n$ 的最小质因子: - 序列 $\{x_k \mod p\}$ 会进入循环(值域有限) - 龟兔指针在 $O(\sqrt{p})$ 步内以高概率碰撞(生日悖论) - 碰撞时 $x_i \equiv y_i \pmod{p}$,故 $|x_i - y_i|$ 含因子 $p$ - $\gcd(|x_i - y_i|, n)$ 至少包含 $p$(除非是 $n$ 本身) --- #### 6. 时间复杂度 - **期望复杂度**:$O(n^{1/4} \log n)$ - **优势**:比试除法($O(\sqrt{n})$)快指数级 - **概率性**:可能失败,但可通过调整 $c$ 和 $x_0$ 重试 --- #### 7. 完整分解示例(Python) ```python import math import random def is_prime(n): """米勒-拉宾素性测试(简化版)""" if n < 2: return False for p in [2, 3, 5, 7, 11]: if n % p == 0: return n == p return True # 简化处理 def pollard_rho(n): if n % 2 == 0: return 2 x = random.randint(2, n-1) y = x c = random.randint(1, n-1) d = 1 while d == 1: x = (x*x + c) % n y = (y*y + c) % n y = (y*y + c) % n d = math.gcd(abs(x - y), n) if d == n: return pollard_rho(n) # 失败重试 return d def factorize(n): if is_prime(n) or n == 1: return [n] d = pollard_rho(n) return factorize(d) + factorize(n//d) # 分解 n = 10403 (101×103) print(factorize(10403)) # 输出: [101, 103] ``` --- ### 关键点总结 1. **随机性**:$c$ 和初始值 $x$ 随机选择,避免死循环。 2. **碰撞检测**:龟兔赛跑法以 $O(1)$ 空间检测循环。 3. **递归分解**:对找到的因子递归调用,直到得到质因子。 4. **效率**:特别适合分解含中等大小因子的大整数(如 RSA 模数)[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值