分解因数算法
文章目录
给定一个正整数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的素因数
}
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| ∣x2−x1∣,然后用这个数去试是否是 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个人,任意两个人生日不同的概率(生日悖论)
放在这里,求因数,怎么操作呢?
我们要产生一系列 [ 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(xm−1)=(xm−12+c)modn
计算若干次后一定会陷入循环
2.如何检查已经陷入了循环?
利用链表上快慢指针检查是否有环的思想
在这里令 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(xi−1)),xj=f(xj−1), x i x_i xi就是那个快傻子, x j x_j xj就是那个慢傻子,他俩就是那个海尔兄弟,舒克贝塔
并且两个傻子从并排起跑到再次相遇,他们的相对路程经理了一个从0到400的过程,遍历了 [ 0 , 400 ] [0,400] [0,400]间的所有值
那么在这里两个快慢指针的差也就遍历了 f ( x ) f(x) f(x)函数能够产生的,在圈上的,所有随机数的差
这个方法叫做"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| ∣xi−xj∣能不能整除 N N N,即检查 ∣ x i − x j ∣ |x_i-x_j| ∣xi−xj∣是不是 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(∣xi−xj∣,N)∣N and gcd(∣xi−xj∣,N)∣∣xi−xj∣,那么只要 ∣ x i − x j ∣ |x_i-x_j| ∣xi−xj∣是N的因数,那么 g c d ( ∣ x i − x j ∣ , N ) gcd(|x_i-x_j|,N) gcd(∣xi−xj∣,N)也是N的因数
并且用最大公因数去和N比划的机会只会比只用 ∣ x i − x j ∣ |x_i-x_j| ∣xi−xj∣去直接比划N更多
对于 ∣ x i − x j ∣ |x_i-x_j| ∣xi−xj∣可以要求放松一点,只要是 ∣ x i − x j ∣ |x_i-x_j| ∣xi−xj∣的任何非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就通过了