Miller-Rabin质数测试 & Pollard-Rho质因数分解

考试遇见卡质因数分解的题了...活久见...毒瘤lun

于是就学了一发qaq

Pollard-Rho分解质因数的话需要依赖另一个算法.

Miller-Rabin质数测试

一个多项式时间的基于随机的质数测试算法.

玄学.

一些依赖的定理

费马小定理

若 \(p \in \mathbb P\) 则 \(\forall a\in \mathbb N^+, a^{p-1} \equiv 1\pmod p\)

它的逆命题基本上是真的, 于是我们可以把这个定理的逆命题作为判断质数的依据.

为啥说"基本上"是真的呢? 因为有些鬼畜的合数 \(n\) 满足 \(\forall a\in \mathbb N^+,a^{n-1}\equiv 1 \pmod n\). 这种鬼畜的合数被称为 Carmichael 数. 前三个 Carmichael 数是 \(561\), \(1105\) 和 \(1729\). OEIS数列编号 A002997 注意到它们可以挺小的...所以只用这个定理的逆命题测试有很大的问题.

二次探测引理

这个名字好像不是很通用...但是我们在这里先这么叫好了...

若 $p\in \mathbb P $ , 则 \(1\bmod p\) 不存在非平凡二次剩余.

我们尝试用它来作为判质数的条件, 那么就可以:

若 \(\forall a^2 \equiv 1 \pmod p, a\equiv \pm1 \pmod p\), 则 \(p \in \mathbb P\)

这个命题也基本上是真的...

但是也有些强伪质数满足上面这个条件...

不过没有合数能同时通过上面的两个测试.

具体用法是先令 \(n-1=2^tu\), 其中 \(u\) 是奇数. 然后随机一个值 \(a\), 把 \(a^u\) 平方 \(t\) 次. 如果某次平方出 \(1\) 了, 那么判断一下平方前的值是否是 \(\pm 1\). 如果是那么就算通过了二次探测. 通过二次探测后刚好求出了 \(a^{2^tu}\) 也就是 \(a^{n-1}\), 判一下是不是 \(1\) 就可以验证费马测试. 如果都是, 那么就称 \(n\) 通过了 \(a\) 的测试, 或者说 "\(a\) 不是 \(n\) 是合数的证据".

实现以及正确率

具体过程大概是

  1. 把乱七八糟的trival情况判掉, 比如 \(\le 2\) 啊, 偶数啊啥的.
  2. 进行 \(s\) 轮测试, 每次随机一个底数 \(a\) 进行上文所述的合并测试.

正确率大概要靠下面这个结论:

若 \(n\) 是一个奇合数, 那么 \(n\) 为合数的证据数量至少为 \(\frac{n-1}2\).

具体证明不详细展开了...算法导论上证了...

那么也就是说, 对于一个合数我们随机选择一个 \(a\) 然后刚好命中不是证据的 \(a\) 的概率小于 \(\frac 1 2\). 进行 \(s\) 轮测试后均命中非证据的概率小于 $2^{-s} $.

实际上不是证据的数的数量远远达不到 \(\frac {n-1} 2\). 能证明的最好结论是非证据的 \(a\) 最多有 \(\frac {n-1} 4\) 个. 而且这个界是能达到的.

所以实际正确率大概是 \(1-4^{-s}\). 一般取 \(s=10\) 效果就已经很好了.

一般用的时候要快速乘. 龟速乘(倍增加法)多半会GG.

代码实现

下面是板子题 LOJ #143. 质数判定 的AC代码

#include <bits/stdc++.h>

typedef long long intEx;

bool MillerRabin(intEx);
inline intEx RandInt(intEx,intEx);
inline intEx Mul(intEx,intEx,intEx);
inline intEx Pow(intEx,intEx,intEx); int main(){
for(intEx x;scanf("%lld",&x)!=EOF;){
if(MillerRabin(x))
puts("Y");
else
puts("N");
}
return 0;
} bool MillerRabin(intEx n){
static const int ROUND=10; if(n<2)
return false;
if(n==2)
return true;
if(!(n&1))
return false; int p=0;
intEx m=n-1;
while(!(m&1))
++p,m>>=1;
for(int k=0;k<ROUND;k++){
intEx pw=Pow(RandInt(1,n-1),m,n);
intEx last=pw;
for(int i=0;i<p;i++){
pw=Mul(pw,pw,n);
if(pw==1&&last!=1&&last!=n-1)
return false;
last=pw;
}
if(pw!=1)
return false;
}
return true;
} inline intEx Mul(intEx a,intEx b,intEx p){
intEx t=a*b-(intEx)((long double)a*b/p+0.5)*p;
return t<0?t+p:t;
} inline intEx RandInt(intEx l,intEx r){
static std::mt19937_64 mt(int(new int));
return std::uniform_int_distribution<intEx>(l,r)(mt);
} inline intEx Pow(intEx a,intEx n,intEx p){
intEx ans=1;
while(n>0){
if(n&1)
ans=Mul(ans,a,p);
a=Mul(a,a,p);
n>>=1;
}
return ans;
}

Pollard-Rho质因数分解

这个算法也是个概率算法. 不过它运行时间是期望的...

首先它也有个依赖的结论

生日悖论与生日攻击

首先是生日悖论. 一个 \(23\) 人的团体存在两人生日相同的概率要大于 \(50\%\). 一个推论是在 \(n\) 个值中随机选取若干个值, \(O(\sqrt n)\) 次后就会有很大概率产生某个值与之前选的值重复的情况.

大概证明可以长这样:

我们补集转化一下, 转而求选出的 \(k\) 个值都不同的概率. 显然它应该长这样:

\[P_n(k)=\prod_{i=0}^{k-1} \left(1-\frac i n\right)
\]

我们只要让这个值小于 \(\frac 1 2\) 就好了. 而由泰勒展开可得:

\[\exp(x)=1+x+\frac{x^2}{2!}+\frac{x^3}{3!}+\cdots
\]

那么对于 \(x> 0\) 有:

\[1+x < \exp(x)
\]

于是就有:

\[P_n(k)=\prod_{i=0}^{k-1}\left(1-\frac i n\right) < \prod_{i=0}^{k-1}\exp\left(-\frac i n\right) = \exp\left(-\sum_{i=0}^{k-1} \frac i n\right) =\exp\left(\frac{-k(k-1)}{2n}\right)
\]

那么我们只要让不等式右边小于 \(\frac 1 2\) 就好了. 那么我们有:

\[\exp\left(\frac{-k(k-1)}{2n}\right) < \frac 1 2
\]

两边取对数解一下就有:

\[k^2-k>2n\ln2
\]

又因为 \(\ln 2\) 是个常数, 于是 \(k=O(\sqrt n)\).

生日攻击是一种现代密码学攻击方法, 就是利用上面的生日悖论来制造Hash碰撞. 主要攻击对象为数字签名. 如果值域为 \(U\) 的话期望生成 \(\sqrt U\) 种不同信息即可发生碰撞. 如果产生Hash碰撞则可以进行伪造数字签名等等一系列攻击行为. Cookie也需要防范这种情况.

主要思想

设我们要对 \(n\) 进行因数分解, \(n\) 存在一个非平凡因子 \(p\) 且 \(p\le n/p\).

我们取一个次数至少为 \(2\) 且包含常数项的多项式函数 \(f(x)\) (比如 \(f(x)=x^2+c\), \(c\) 随机取), 设 \(g(x)=f(x)\bmod n\), 用 \(g\) 来生成伪随机数. 方法是随机选取一个初始值 \(r\) 不断将新的 \(x\) 代入 \(g(x)\) , 也就是 \(x_1=g(r), x_2=g(g(r)),x_3=g(g(g(r)))\) . 那么我们可以认为数列 \(\langle x_k\rangle\) 看上去是随机的. 而且 \(x_k=g(x_{k-1})\).

也就是说这个伪随机数列只和它的上一个输出有关. 从一个相同的初值可以得到相同的序列.

由于 \(p\mid n\), 所以数列 \(\langle x_k \bmod p\rangle\) 也满足这个性质, 即 \(x_k\equiv g(x_{k-1}) \pmod p\). 因为这个伪随机数生成器的状态只和上一个输出有关, 那么只要生成的新值命中了之前已经生成的值, 那么这个随机数列就会出现环.

由于 \(p\) 比较小, \(\langle x_k \bmod p \rangle\) 有很大概率比 \(\langle x_k \rangle\) 更早出现环 (只要新生成的 \(x\) 对 \(p\) 取模的值命中以前出现的值就会出现环, 而 \(p\) 不会超过 \(\sqrt n\)). 根据生日悖论, 在大约 \(\sqrt p\) 次随机后就会有很大概率出现. 如果出现这种情况, 那么我们就获得了两个值 \(x_a \equiv x_b \pmod p\). 于是它们之间的差是可以被 \(p\) 整除的. 我们取 \(p=\gcd (\left|x_a-x_b\right|,n)\) 即可.

至于这个判环的过程, 我们可以使用神奇的Floyd判环法. 建立两个哨兵 \(x_a\) 和 \(x_b\), 每次令 \(x_a\) 前进一步, 令 \(x_b\) 前进两步, 如果 \(x_a=x_b\), 则说明走到了环上.

但是我们并不知道 \(p\) 是多少, 所以并不能直接判断 \(x_a = x_b\) 来判断 \(\langle x_k \bmod p\rangle\) 是否进入环. 但是根据上文所述, 如果出现了 \(x_a\equiv x_b \pmod p\), 那么就一定出环了, 这个时候 \(p\mid \gcd (\left|x_a-x_b\right|,n)\), 我们便成功获得了一个 \(n\) 的非平凡因子.

不过有时候我们可能会手气不佳, 抽到的 \(c\) 和 \(r\) 生成的 \(\langle x_k \rangle\) 和 \(\langle x_k \bmod p\rangle\) 可能会同时进入环(显然前者不可能比后者进环更快, 最多同时). 不难发现这个环长也是 \(\sqrt p\) 级别的. 但是由于这时候找到的 \(x_a\) 和 \(x_b\) 是相等的, 于是并不能带来什么信息. 在这个时候有两种可能, 第一是 \(n\in \mathbb P\), 第二是脸黑.

普通的Pollard-Rho会到此为止而不提供任何信息.

期望的时间复杂度是 \(O(n^{1/4}\log n)\) 的, 那个 \(\log\) 是 \(\gcd\) 的时候来的.

具体实现

首先上文所述的过程只能用来找非平凡因子而非质因子.

我们把它和 Miller-Rabin 质数测试结合起来就可以进行质因数分解了. 大致流程如下:

  1. 用 Miller-Rabin 判断当前 \(n\) 是否是质数. 如果是, 丢进答案集合, 跑路.
  2. 随机选择一对 \(c\) 和 \(r\), 进行上文所述的测试过程.
  3. 如果找到了一个非平凡因子 \(p\), 递归求解 \(p\) 和 \(n/p\), 然后跑路.
  4. 否则, 承认自己脸黑, 返回第 2 步再来一次.

代码片段

void PollardRho(intEx n,std::vector<intEx>& factor){
if(MillerRabin(n))
factor.push_back(n);
else while(true){
intEx a,b,c=RandInt(0,n-1);
a=b=RandInt(0,n-1);
auto f=[=](intEx x){
return (Mul(x,x,n)+c)%n;
};
b=f(b);
while(a!=b){
intEx delta=std::abs(a-b);
delta=std::__gcd(delta,n);
if(delta!=1){
PollardRho(delta,factor);
PollardRho(n/delta,factor);
return;
}
a=f(a);
b=f(f(b));
}
}
}

洛谷板子过于SXBK地卡常于是没过TwT...我活该大常数...

LOJ板子更加SXBK地把数据出到了 \(1\times 10^{30}\)...跑路了QAQ...

一般应用没啥问题(吧)...

参考资料

[学习笔记] Miller-Rabin质数测试 & Pollard-Rho质因数分解的更多相关文章

  1. HDU 3864 D_num Miller Rabin 质数推断+Pollard Rho大整数分解

    链接:http://acm.hdu.edu.cn/showproblem.php? pid=3864 题意:给出一个数N(1<=N<10^18).假设N仅仅有四个约数.就输出除1外的三个约 ...

  2. POJ1811_Prime Test【Miller Rabin素数测试】【Pollar Rho整数分解】

    Prime Test Time Limit: 6000MS Memory Limit: 65536K Total Submissions: 29193 Accepted: 7392 Case Time ...

  3. HDU1164_Eddy&#39;s research I【Miller Rabin素数测试】【Pollar Rho整数分解】

    Eddy's research I Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...

  4. STM32学习笔记(十) CAN通讯测试(环回模式)

    1.CAN通讯的理解 想学习CAN通讯,那么要对通讯协议有一定的认知.通讯协议是指通信双方对数据传送控制的一种约定.约定中包括对数据格式,同步方式,传输速度,传送步骤,检纠错方式以及控制字符定义等问题 ...

  5. AMQ学习笔记 - 17. 事务的测试

    概述 对事务机制进行测试. 测试实例 测试实例 结果预测 发送正常 3条消息入队 发送异常 0条消息入队 接收正常 3条消息出队 接收异常 0条消息出队 demo设计 设计图 测试分工 测试类 测试方 ...

  6. AMQ学习笔记 - 18. 持久化的测试

    概述 对持久化的有效性进行测试. 测试实例 测试实例 结果预测 持久化递送 重启ActiveMQ后,消息还在队列中 非持久化递送 重启ActiveMQ后,消息不在队列中 demo设计 jms-prod ...

  7. IGS_学习笔记08_IREP通过soapUI测试客户化Web Service调用(案例)

    20150819 Created By BaoXinjian

  8. IGS_学习笔记07_IREP通过页面测试客户化Web Service调用(案例)

    20150819 Created By BaoXinjian

  9. Spring学习笔记6——注解方式测试

    需要下载junit-4.12.jar和hamcrest-all-1.3.jar,将下载好的包导入到项目当中. 修改TestSpring, 并运行1. @RunWith(SpringJUnit4Clas ...

  10. spring框架学习笔记1:搭建测试

    Spring框架介绍: Spring框架涵盖了web.service.dao三层,本身是一个存放对象的容器 形象来说:Spring是项目中对象管家 Spring框架的两大核心思想:控制反转(IOC). ...

随机推荐

  1. ASP.NET MVC ScriptBundle 不能加载.min.js文件

    比如我用 bundles.Add(new ScriptBundle("~/bundles/easyui").Include( "~/Content/easyui/jque ...

  2. Nodejs书写爬虫工具

    看了几天的nodejs,的确是好用,全当是练手了,就写了一个爬虫工具. 爬虫思路都是一致的,先抓取页面数据,然后分析页面,获取到所需要的数据,最后获得这些数据,是写入到硬盘,还是显示到网页,自己看着办 ...

  3. 基于统计的无词典的高频词抽取(二)——根据LCP数组计算词频

    接着上文[基于统计的无词典的高频词抽取(一)——后缀数组字典序排序],本文主要讲解高频子串抽取部分. 如果看过上一篇文章的朋友都知道,我们通过 快排 或 基数排序算出了存储后缀数组字典序的PAT数组, ...

  4. vim基本命令速查表

    来源:https://github.com/skywind3000/awesome-cheatsheets/blob/master/editors/vim.txt ################## ...

  5. 设计模式之模版方法模式(Template Method Pattern)

    一.什么是模版方法模式? 首先,模版方法模式是用来封装算法骨架的,也就是算法流程 既然被称为模版,那么它肯定允许扩展类套用这个模版,为了应对变化,那么它也一定允许扩展类做一些改变 事实就是这样,模版方 ...

  6. C# 代码风格要求

    一个.cs源文件至多定义两个类型 所有命名空间.类型名称使用Pascal风格(单词首字母大写),私有方法.受保护方法,仍使用Pascal风格命名 本地变量.方法参数名称使用Camel风格(首字母小写, ...

  7. Java 类型转换工具类(持续更新)

    简介 将项目中用到的类型转换做个记录. 详细代码 @Component public class TypeUtil { // [start]字符串转各种格式 // 字符串转日期(格式:"yy ...

  8. XML文件之创建

    1.创建XML文档对象XmlDocument doc=new XmlDocument() 2.创建XML根节点变量XmlElement xmlElement 3.判断XML文件是否已经存在 1)若存在 ...

  9. Jmeter——参数化的9种方法

    本文由作者张迎贞授权网易云社区发布. 一.用户定义的变量 1.右键快捷菜单中选择 添加-配置元件-用户自定义变量. 用户自定义变量中的定义的所有参数的值在测试计划的执行过程中不能发生取值的改变,因此一 ...

  10. GCC升级问题解决:configure: error: Building GCC requires GMP 4.2+, MPFR 2.4.0+ and MPC 0.8.0+.,mpfr2.4.0

    如果遇到类似问题: configure: error: Building GCC requires GMP 4.2+, MPFR 2.4.0+ and MPC 0.8.0+.,mpfr2.4.0 解决 ...