Pollard Rho 总结

Upd

这是我很久以前写的,其实当时也是感性理解,现在修改,希望能加深理解。

至于原来的,算了,不删了。

作用:大整数质因数分解

暴力太慢了,于是有大佬发明了这个

Gcd

一个合数一定有一个质因子小于\(\sqrt{n}\),所以 n 至少存在\(\sqrt{n}\)个数与 n 有大于 1 的公约数

随机生成

\(x=rand(),c=rand()\) 随机生成一对数,然后\(y=x,x=x^2+c,g=\gcd(|y-x|,n)\) 经过测试发现 \(g>1\) 的概率接近 \(n^{\frac{1}{4}}\)
具体好像是生日悖论,本蒟蒻不懂。

判环

注意可能会出现环,即出现出现了的数,所以需要判定
用倍增思想,y 现记下 x 的位置,然后 x 跑当前次数一倍的次数。
跑完了之后 y 再记下 x 的位置,次数再翻倍,当 x 等于 y 时,遇到环了

优化

这样应该会 TLE ,经过观察会发现 Gcd 带的 log 很拖时间
\(|y-x|\)有和 n 的公约数,那么若干个\(|y-x|\)乘在一起并取余 n 也有和 n 的公约数
可以用 z 来累计\(|y-x|\)的乘积,过 127 次在来 Gcd
至于为什么是 127 次,,玄学

注意

  1. 可能不用 127 次就遇到环了,由于上面打了倍增,可以再生成了许多次时判环。
  2. z 为 0 时没必要继续

分解

  1. 若 n 时质数(用 Miller-Rabin ),直接返回
  2. Pollard Rho 求出一个非平凡因子
  3. 把非平凡因子消掉,继续分解

Code

Luogu P4718

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef long double LD;
inline LL Abs(LL x) { return x<0?-x:x; }
inline LL Mul(uLL x,uLL y,LL p) { return (x*y-(uLL)((LD)x/p*y)*p+p)%p; }
inline LL Pow(LL x,LL y,LL p){
	register LL res=1;
	for(;y;y>>=1,x=Mul(x,x,p))
	if(y&1)res=Mul(res,x,p);
	return res;
}
inline bool Mr(LL n,LL p) {
    if(Pow(p,n-1,n)!=1)return 0;
    register LL q=n-1,o;
    while(!(q&1)) {
        q>>=1,o=Pow(p,q,n);
        if(o!=1 && o!=n-1)return 0;
        if(o==n-1)return 1;
    }
    return 1;
}
inline bool Prime(LL n) {
    if(n<2)return false;
    if(n==2||n==3||n==5||n==7||n==43)return true;
    return Mr(n,2)&&Mr(n,3)&&Mr(n,5)&&Mr(n,7)&&Mr(n,43);
}
inline LL Gcd(LL x,LL y) {
    return y?Gcd(y,x%y):x;
}
inline LL Rho(LL n) {
    LL x,y,z,c,g;
    register int p,q;
    for(;;) {
        x=y=rand()%n,c=rand()%n;
        p=0,q=z=1;
        while(++p) {
            x=(Mul(x,x,n)+c)%n;
            z=Mul(z,Abs(y-x),n);
            if(x==y || !z)break;
            if(!(p%127) || p==q) {
                g=Gcd(z,n);
                if(g>1)return g;
                if(p==q)q<<=1,y=x;
            }
        }
    }
}
LL ans;
inline void Dfs(LL n) {
    if(n<=ans)return;
    if(Prime(n)) { ans=n; return; }
    LL p=Rho(n);
    while(n%p==0)n/=p;
    Dfs(p),Dfs(n);
}
int main() {
    srand(20080816);
    LL n,T;
    scanf("%lld",&T);
    while(T--) {
        scanf("%lld",&n);
        ans=0;Dfs(n);
        if(ans==n)puts("Prime");
        else printf("%lld\n",ans);
    }
    return 0;
}

重写版本

生日悖论

23 人的班级,就有 50% 以上的概率,存在两个人生日相同。

证明:设 \(P_n\) 表示选 \(n\) 个人,不存在两人生日相同的概率。

\[P_n=\prod_{1\le i<n}1-\dfrac{i}{365} \]

由均值不等式:

\[\sqrt[n-1]{\prod_{1\le i<n}1-\dfrac{i}{365}}\le \dfrac{1}{n-1}\sum_{i=1}^{n-1}1-\dfrac{i}{365} \]

所以

\[P_n\le \left(\dfrac{1}{n-1}\times \left[(n-1)-\dfrac{1}{2}\times\dfrac{n}{365}\times(n-1)\right]\right)^{n-1} = (1-\dfrac{n}{730})^{n-1} \]

由泰勒展开的一个不等式: \(e^{-x}>1-x\)

\[P_n<e^{-\frac{n}{730}*(n-1)} \]

\(n=23\) 时, \(P_n<0.5\) ,所以两个人生日相同的概率超过了 \(50\%\)

扩展

\(n\) 个数中选出 \(k\) 个,存在两个数相等的概率,当 \(k\) 接近 \(\sqrt{n\times\ln 2}\) 时就接近 \(50\%\)

两个数相等,可以看成差为 0

进一步,从 \(n\) 个数中选出 \(k\) 个,存在两个数的差是 \(n\) 的因子的概率,当 \(k\) 接近 \(\sqrt{n\times\ln 2}\) 时接近 \(50\%\)

有了生日悖论,本算法的正确性得到保证

随机选取

选取 \(k\) 个数 \(x_1,x_2,\cdots,x_k\)

如果朴素地选取 \(x_i-x_j\) 能够整除 \(n\) ,期望 \(\sqrt n\) 个数,同时又需要枚举判断。复杂度不理想。

现在有一个方法:判断是否存在 \(\gcd(|x_i-x_j|,n)>1\) 的情况,如果有则 \(\gcd(|x_i-x_j|,n)\) 是一个 \(n\) 的因子

这样只需选取 \(n^{\frac{1}{4}}\) 个数。

储存、判断成了问题。

pollard rho

pollard rho 只将两个数保存,每次检查连续的两个数。

此处有一个神奇的伪随机函数

\[f(x)=(x^2+a)\;mod\;n \]

\(a\) 为常数。

\(f(x)\) 生成序列 \(\{x_i\}\) ,规则是 \(x_1=\text{rand}(),x_{i+1}=f(x_i)\)

判环

注意:\(f(x)\) 是伪随机函数,因为在模 \(n\) 意义下,会出现环

采用 floyd 判环,让 \(x,y\) 来走,\(y\) 速度是 \(x\) 的两倍,当 \(y\) 第一次追上 \(x\) 就至少走了 1 圈。

得到了一个基于判环的代码

inline LL Rho(LL n) {
    LL x, y, c, g;
    for (;;) {
        x = y = rand(), c = rand();
        y = (Mul(y, y, n) + c) % n;
        while (x ^ y) {
            g = Gcd(Abs(x - y), n);
            if (g > 1) return g;
            x = (Mul(x, x, n) + c) % n;
            y = (Mul(y, y, n) + c) % n;
            y = (Mul(y, y, n) + c) % n;
            // y goes double faster
        }
    }
}

优化

板子题都过不了!?(数据太强了)

\(\gcd\) 是带一个 \(\log\) 的,此优化减少 \(\gcd\) 的次数。

如果 \(\gcd(a,b)>1\) 由欧几里德算法,\(\gcd(a,b)=\gcd(ac,b)>1\) ,其中 \(c\in\mathbb{N}^+\)

更进一步, \(\gcd(a,b)=\gcd(ac\mod b,b)>1\)

可以将所有的 \(|x-y|\) 在模 \(n\) 意义下乘起来,再求 \(\gcd\) ,需要适当地选取相乘次数。

倍增

在路径上倍增地选择一段 \([2^{k-1},2^k]\) 的区间。

\(\gcd\) 取的是 \(\forall i\in[2^{k-1},2^k],|x_i-x_l|\) ,一次就有 \(2^{k-1}\) 个累积,倍增一定程度上优化了复杂度

至于上界,经过实验,\(127=2^7-1\) 是较优的选择

inline LL Rho(LL n) {
    LL x,y,z,c,g;
    register int p,q;
    for(;;) {
        x=y=rand()%n,c=rand()%n;
        p=0,q=z=1;
        while(++p) {
            x=(Mul(x,x,n)+c)%n;
            z=Mul(z,Abs(y-x),n);
            if(x==y || !z)break;
            if(!(p%127) || p==q) {
                g=Gcd(z,n);
                if(g>1)return g;
                if(p==q)q<<=1,y=x;
            }
        }
    }
}

完整代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef long double LD;
inline LL Abs(LL x) { return x<0?-x:x; }
inline LL Mul(uLL x,uLL y,LL p) { return (x*y-(uLL)((LD)x/p*y)*p+p)%p; }
inline LL Pow(LL x,LL y,LL p){
	register LL res=1;
	for(;y;y>>=1,x=Mul(x,x,p))
	if(y&1)res=Mul(res,x,p);
	return res;
}
inline bool Mr(LL n,LL p) {
    if(Pow(p,n-1,n)!=1)return 0;
    register LL q=n-1,o;
    while(!(q&1)) {
        q>>=1,o=Pow(p,q,n);
        if(o!=1 && o!=n-1)return 0;
        if(o==n-1)return 1;
    }
    return 1;
}
inline bool Prime(LL n) {
    if(n<2)return false;
    if(n==2||n==3||n==5||n==7||n==43)return true;
    return Mr(n,2)&&Mr(n,3)&&Mr(n,5)&&Mr(n,7)&&Mr(n,43);
}
inline LL Gcd(LL x,LL y) {
    return y?Gcd(y,x%y):x;
}
// inline LL Rho(LL n) {
//     LL x, y, c, g;
//     for (;;) {
//         x = y = rand(), c = rand();
//         y = (Mul(y, y, n) + c) % n;
//         while (x ^ y) {
//             g = Gcd(Abs(x - y), n);
//             if (g > 1) return g;
//             x = (Mul(x, x, n) + c) % n;
//             y = (Mul(y, y, n) + c) % n;
//             y = (Mul(y, y, n) + c) % n;
//         }
//     }
// }
inline LL Rho(LL n) {
    LL x,y,z,c,g;
    register int p,q;
    for(;;) {
        x=y=rand()%n,c=rand()%n;
        p=0,q=z=1;
        while(++p) {
            x=(Mul(x,x,n)+c)%n;
            z=Mul(z,Abs(y-x),n);
            if(x==y || !z)break;
            if(!(p%127) || p==q) {
                g=Gcd(z,n);
                if(g>1)return g;
                if(p==q)q<<=1,y=x;
            }
        }
    }
}
LL ans;
inline void Dfs(LL n) {
    if(n<=ans)return;
    if(Prime(n)) { ans=n; return; }
    LL p=Rho(n);
    while(n%p==0)n/=p;
    Dfs(p),Dfs(n);
}
int main() {
    srand(time(0));
    LL n,T;
    scanf("%lld",&T);
    while(T--) {
        scanf("%lld",&n);
        ans=0;Dfs(n);
        if(ans==n)puts("Prime");
        else printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2021-01-26 20:03  小蒟蒻laf  阅读(132)  评论(0)    收藏  举报