学习 Pollard-Rho
前言
- woc这月赛是啥a
- msjing在写 Ynoi 需要用这个于是来学了
- pb好强强强TTC好强强强
\(\mathcal{Miller-Rabin}\) 算法
- 这是一个判素数的算法,想要学 \(\mathcal{Pollard-Rho}\) 就要先学这个
- 不是知更鸟(\(\mathcal{robin}\))哦
- 学这个要先学费马小,这个百度一下就行了
- \(\mathcal{Rabin}\) 算法本质是一种随机化算法,不过我们要先介绍一个错误判素数的方法,就是上面说的费马小
- 我们知道费马小长这样:
\[a^{p-1} \equiv 1 \>\>\> (\!\!\!\!\!\mod p)
\]
- 其中 \(p\) 为素数
- 我们发现貌似使这个成立的 \(p\) 都是素数
- 但是是假的,因为一类数被构造了
\(\mathcal{Carmichael}\) 数
-
这个数的定义是:对于一个合数 \(n\),所有与 \(n\) 互质的正整数 \(a\),都满足费马小,那称 \(n\) 为 \(\mathcal{Carmichael}\) 数
-
然后费马小就假了
-
所以我们要换一个了
-
但是其实是没啥好办法了,于是请出随机化
-
不过我们要先证一个东西
-
我们设 \(x\) 在 \(p\) 的剩余系中,当 \(p\) 为素数时,\(x\) 只有两个解 \(1\) 或 \(p-1\),这个是充要的,即:
\[x^2 \equiv 1 \>\>\>(\!\!\!\!\! \mod p) \Longleftrightarrow x = 1 \>\>\> or \>\>\> p-1
\]
- 这个叫二次探测定理
- 我们证明一下(TTC好强谢谢你喵)
证明
- 先证必要性
- \(x = 1\) 是显然成立的
- 我们把 \(x = p-1\) 带入,有:
\[(p-1)^2
\]
- 完全平方展开有:
\[p^2 - 2p + 1
\]
- 提出 \(p\),有:
\[p(p - 2) + 1
\]
- \(\!\!\!\mod p\),余 \(1\),证毕
- 再证充分性
- 我们把柿子换个形式:
\[x^2 = k \times p + 1
\]
- 其中,\(k\) 是一个整数
- 移项,有:
\[x^2 - 1 = k \times p
\]
- 即:
\[x^2 - 1 \mid p
\]
- 平方差展开,有:
\[(x + 1)(x - 1) \mid p
\]
- 即:
\[(x + 1) \mid p \>\>\> or \>\>\> (x - 1) \mid p
\]
- 前面的是 \(x = p + 1\),后面的是 \(x = p - 1\),由于定义 \(x\) 在 \(p\) 剩余系内,所以前面解为 \(x = 1\),证毕
- 有了这个,我们就可以判素数了
- 具体做法很神奇,我们直接说步骤
- 枚举 \(k\) 个数满足 \(1 < a_i < p\),带入验证
- 如何带入验证
- 我们令 \(p - 1 = 2^k \times b\),\(k\) 尽可能大,\(b\) 为整数
- 我们可以拆出 \(p - 1\) 的约数个数为 \(k\),将一个 \(a\) 带入,并与拆剩下的数做快速幂运算,结果设为 \(s\),如果 \(s = 1\),成立,\(p\) 为素数
- 不成立,枚举 \(k\) 次,每次让 \(s\) 自乘,就是把幂再累加上去,每次判 \(s\) 是否等于 \(p - 1\),成立,\(p\) 为素数,直接出,枚举完 \(k\) 后还不成立,\(p\) 不是素数,注意先判再自乘
- \(a\) 的个数 \(k\) 大概在 \(8\),直接枚举素数就行,记得特判一下 \(p\) 是否是 \(a\) 中的数
- 这个算法就没了
- 欸,但是这样不会出错吗
- e,事实上,在
long long内的数这个出错概率基本为 \(0\),实际出错率为每次最多 $ 1 \over 4$ - 复杂度 \(O(k \log n)\)
板子SP288 PON - Prime or Not
点击查看代码
- 可能会爆,所以用龟速乘
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn=2e6+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int a[9]={0,2,3,5,7,11,13,17,19};
int mul(int x,int y,int p)
{
int res=0;
while (y)
{
if (y&1) (res+=x)%=p;
(x+=x)%=p;
y>>=1;
}
return res;
}
int power(int x,int y,int p)
{
int res=1;
while (y)
{
if (y&1) res=mul(res,x,p);
x=mul(x,x,p);
y>>=1;
}
return res;
}
int chk(int p,int a)
{
int k=p-1,cnt=0;
while (!(k&1))
cnt++,k>>=1;
int x=power(a,k,p);
if (x == 1) return 1;
for (int i=1;i<=cnt;i++)
{
if (x == p-1) return 1;
x=mul(x,x,p);
}
return 0;
}
int prime(int x)
{
for (int i=1;i<=8;i++)
{
if (x == a[i]) return 1;
if (!(x%a[i])) return 0;
if (!chk(x,a[i])) return 0;
}
return 1;
}
signed main()
{
int T=read();
while (T--)
{
int n=read();
prime(n) ? puts("YES") : puts("NO");
}
return 0;
}
-
\(\mathcal{Rabin}\) 就没了,接下来是 \(PR\)
-
不过我们在学这个之前,要先学 \(\mathcal{Floyd}\) 判圈算法
\(\mathcal{Floyd}\) 判圈算法
- \(\mathcal{Floyd}\) 判圈算法,又称龟兔赛跑算法,由美国计算机科学家 \(\mathcal{Robert\>\>\>W.Floyd}\) 提出,那个最短路也是他提出的
- \(\mathcal{Floyd}\) 判圈算法一般用于判链表是否存在环
- 那 \(\mathcal{Floyd}\) 判圈算法是什么原理呢?
- 我们开两个指针,一个快一个慢,快的每次走两步,好比兔子,慢的每次走一步,好比乌龟
- 当没有环时,慢指针一定追不上快指针,而有环时,快指针会在环内追上慢指针
- 实现还好,msjing不打算写了
- 复杂度线性
\(\mathcal{Pollard-Rho}\)
- 这个算法可以以 \(O(n^{1 \over 4})\) 的期望时间算出一个 \(n\) 的非平凡因子
非平凡因子:若 \(x\) 能整除 \(n\) 且 \(1<x<n\) ,则称 \(x\) 是 \(n\) 的非平凡因子
- 这个算法本质是通过一个迭代的函数(比如 \(x_i = f(x_{i-1})\))在取模(模数为 \(n\) 的最小质因子,设为 \(p\))意义下进行迭代,由于有取模限制,所以 \(x\) 的取值是有限的,因此它会进一个环,期望进环时间为 \(O(\sqrt p)\)
- 当有两个数满足:\(x_i \equiv x_j \>\>\>(\!\!\!\! \mod p)\) 时,如果 \(x_i \neq x_j\),那就是找到环了,同时,有 \(p \mid \gcd(|x_i − x_j|,n)\),如果 \(\gcd(|x_i − x_j|,n) > 1\),\(p\) 就是 \(n\) 的一个非平凡因子
- \(\mathcal{Pollard-Rho}\) 算法通过一个序列 \(a_i = a_{i-1}^2 + c \>\>\>(\!\!\!\! \mod n),a_0 = x\) 来实现
- 我们通过随机参数,发现点的分布有周期性(desmos驯服失败,图没搓出来)
- 那对于一些值,会形成有个尾巴的环,和 \(\rho\) 很像
- 期望进环的时间证明需要生日悖论,介绍一下
生日悖论
- 在不少于 \(23\) 人的群体中出现相同生日的概率超过 \(50%\),\(60\) 人时概率可达 \(99%\)
- 其函数为:
\[f\left(x\right)\ =\ 1-\ e^{-\frac{x\left(x-1\right)}{2 \times N}} \>\>(N = 365)
\]
- 可以用 desmos 画一下

- 比例没设好
woc我真要证这个吗- 对于这个问题,用生日悖论可以证明期望进环时间,
但msjing不会证 - 那你直接随机参数带入验证,迭代计算,就可以得到结论
- 但是你发现有可能会由于运气
脸黑而导致找不到环,所以我们用上面的 \(\mathcal{Floyd}\) 判环 - 复杂度是 \(O(\sqrt p)\) 的
- 不过你要把 \(4\) 判掉,要不然跑不出来
- 但是算法复杂度还是有点高,我们发现 \(\gcd\) 有点慢
- 发现由于算法是与 \(n\) 取 \(\gcd\),所以我们发现如果将 \(|x_i - x_j|\) 累加起来再做 \(\gcd\) 是不影响的,求出来的数仍然是 \(n\) 的非平凡因子
- 累加阈值可以调,一般用 \(128\)
- 整个算法就没有了,我们去把板子写了
板子P4718 【模板】Pollard-Rho
找个好看的代码贺一下
点击查看代码
- \(\mathcal{Rabin}\) 算法的伪随机 \(a\) 个数设为 \(8\) 会掉一个,设为 \(9\) 是对的,
msjing设为 \(8\) 掉了 __int128好像没法用 \(abs\),所以手写了一个
#include <bits/stdc++.h>
#define int __int128
using namespace std;
constexpr int maxn=2e6+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
namespace MR
{
int a[11]={0,2,3,5,7,11,13,17,19,23,29};
int power(int x,int y,int p)
{
int res=1;
while (y)
{
if (y&1) (res*=x)%=p;
(x*=x)%=p;
y>>=1;
}
return res;
}
int chk(int p,int a)
{
int k=p-1,cnt=0;
while (!(k&1))
cnt++,k>>=1;
int x=power(a,k,p);
if (x == 1) return 1;
for (int i=1;i<=cnt;i++)
{
if (x == p-1) return 1;
(x*=x)%=p;
}
return 0;
}
int prime(int x)
{
for (int i=1;i<=10;i++)
{
if (x == a[i]) return 1;
if (!(x%a[i])) return 0;
if (!chk(x,a[i])) return 0;
}
return 1;
}
}using namespace MR;
namespace PR
{
mt19937 rd(time(0));
int f(int x,int c,int p) {return (x*x%p+c)%p;}
int _abs(int x,int y)
{return x-y<0 ? y-x : x-y;}
int rho(int n)
{
int mo=n;
int x=rd()%mo,c=rd()%(mo-1)+1,p=1;
for (int i=2,j=2,d=x;i;i++)
{
x=f(x,c,mo);
(p*=_abs(x,d))%=mo;
if (!(i%127) && __gcd(p,n)!=1)
return __gcd(p,n);
if (i == j)
{
j<<=1,d=x;
if (__gcd(p,n)!=1)
return __gcd(p,n);
}
}
}
int pr(int n)
{
if (prime(n)) return n;
int p=n;
while (p == n) p=rho(n);
return max(pr(p),pr(n/p));
}
}using namespace PR;
signed main()
{
int T=read();
while (T--)
{
int n=read();
int ans=pr(n);
if (ans == n) puts("Prime");
else printf("%lld\n",(long long)ans);
}
return 0;
}
例题P5071 [Ynoi Easy Round 2015] 此时此刻的光辉
- 就是因为这个我才学的
- 这里看
- 这真是个黑科技我瞎搞做法能过
后记
- 没了,写 Ynoi 去了
- upd on 8.22 21:30:写完了,来补例题
$\mathscr{msjing}$

浙公网安备 33010602011771号