素数与素性测试(Miller-Rabin测试)
转载自Matrix大牛的博客 把代码翻译成C++
http://www.matrix67.com/blog/archives/234
题目链接:
http://hihocoder.com/problemset/problem/1287
一个数是素数(也叫质数),当且仅当它的约数只有两个——1和它本身。规定这两个约数不能相同,因此1不是素数。对素数的研究属于数论范畴,你可以 看到许多数学家没事就想出一些符合某种性质的素数并称它为某某某素数。整个数论几乎就围绕着整除和素数之类的词转过去转过来。对于写代码的人来说,素数比 想像中的更重要,Google一下BigPrime或者big_prime你总会发现大堆大堆用到了素数常量的程序代码。平时没事时可以记一些素数下来以 备急用。我会选一些好记的素数,比如4567, 124567, 3214567, 23456789, 55566677, 1234567894987654321, 11111111111111111111111 (23个1)。我的手机号前10位是个素数。我的网站域名的ASCII码连起来(77 97 116 114 105 120 54 55 46 99 111 109)也是个素数。
素数有很多神奇的性质。我写5个在下面供大家欣赏。
1. 素数的个数无限多(不存在最大的素数)
证明:反证法,假设存在最大的素数P,那么我们可以构造一个新的数2 * 3 * 5 * 7 * … * P + 1(所有的素数乘起来加1)。显然这个数不能被任一素数整除(所有素数除它都余1),这说明我们找到了一个更大的素数。
2. 存在任意长的一段连续数,其中的所有数都是合数(相邻素数之间的间隔任意大)
证明:当0<a<=n时,n!+a能被a整除。长度为n-1的数列n!+2, n!+3, n!+4, …, n!+n中,所有的数都是合数。这个结论对所有大于1的整数n都成立,而n可以取到任意大。
3. 所有大于2的素数都可以唯一地表示成两个平方数之差。
证明:大于2的素数都是奇数。假设这个
数是2n+1。由于(n+1)^2=n^2+2n+1,(n+1)^2和n^2就是我们要找的两个平方数。下面证明这个方案是唯一的。如果素数p能表示成
a^2-b^2,则p=a^2-b^2=(a+b)(a-b)。由于p是素数,那么只可能a+b=p且a-b=1,这给出了a和b的唯一解。
4. 当n为大于2的整数时,2^n+1和2^n-1两个数中,如果其中一个数是素数,那么另一个数一定是合数。
证明:2^n不能被3整除。如果它被3除余1,那么2^n-1就能被3整除;如果被3除余2,那么2^n+1就能被3整除。总之,2^n+1和2^n-1中至少有一个是合数。
5. 如果p是素数,a是小于p的正整数,那么a^(p-1) mod p = 1。
这个证明就有点麻烦了。
首
先我们证明这样一个结论:如果p是一个素数的话,那么对任意一个小于p的正整数a,a, 2a, 3a, …,
(p-1)a除以p的余数正好是一个1到p-1的排列。例如,5是素数,3, 6, 9, 12除以5的余数分别为3, 1, 4,
2,正好就是1到4这四个数。
反证法,假如结论不成立的话,那么就是说有两个小于p的正整数m和n使得na和ma除以p的余数相同。不妨假设n>m,则p可以整除a(n-m)。但p是素数,那么a和n-m中至少有一个含有因子p。这显然是不可能的,因为a和n-m都比p小。
用同余式表述,我们证明了:
(p-1)! ≡ a * 2a * 3a * … * (p-1)a (mod p)
也即:
(p-1)! ≡ (p-1)! * a^(p-1) (mod p)
两边同时除以(p-1)!,就得到了我们的最终结论:
1 ≡ a^(p-1) (mod p)
可惜最后这个定理最初不是我证明的。这是大数学家Fermat证明的,叫做Fermat小定理(Fermat's Little
Theorem)。Euler对这个定理进行了推广,叫做Euler定理。Euler一生的定理太多了,为了和其它的“Euler定理”区别开来,有些地
方叫做Fermat小定理的Euler推广。Euler定理中需要用一个函数f(m),它表示小于m的正整数中有多少个数和m互素(两个数只有公约数1称
为互素)。为了方便,我们通常用记号φ(m)来表示这个函数(称作Euler函数)。Euler指出,如果a和m互素,那么a^φ(m) ≡ 1
(mod
m)。可以看到,当m为素数时,φ(m)就等于m-1(所有小于m的正整数都与m互素),因此它是Fermat小定理的推广。定理的证明和Fermat小
定理几乎相同,只是要考虑的式子变成了所有与m互素的数的乘积:m_1 * m_2 … m_φ(m) ≡ (a * m_1)(a * m_2) …
(a * m_φ(m)) (mod m)。我为什么要顺便说一下Euler定理呢?因为下面一句话可以增加我网站的PV:这个定理出现在了The Hundred Greatest Theorems里。
谈到Fermat小定理,数学历史上有很多误解。很长一段时间里,人们都认为Fermat小定理的逆命题是正确的,并且有人亲自验证了
a=2,
p<300的所有情况。国外甚至流传着一种说法,认为中国在孔子时代就证明了这样的定理:如果n整除2^(n-1)-1,则n就是素数。后来某个英
国学者进行考证后才发现那是因为他们翻译中国古文时出了错。1819年有人发现了Fermat小定理逆命题的第一个反例:虽然2的340次方除以341余
1,但341=11*31。后来,人们又发现了561, 645,
1105等数都表明a=2时Fermat小定理的逆命题不成立。虽然这样的数不多,但不能忽视它们的存在。于是,人们把所有能整除2^(n-1)-1的合
数n叫做伪素数(pseudoprime),意思就是告诉人们这个素数是假的。
不满足2^(n-1) mod n =
1的n一定不是素数;如果满足的话则多半是素数。这样,一个比试除法效率更高的素性判断方法出现了:制作一张伪素数表,记录某个范围内的所有伪素数,那么
所有满足2^(n-1) mod n = 1且不在伪素数表中的n就是素数。之所以这种方法更快,是因为我们可以使用二分法快速计算2^(n-1)
mod n
的值,这在计算机的帮助下变得非常容易;在计算机中也可以用二分查找有序数列、Hash表开散列、构建Trie树等方法使得查找伪素数表效率更高。
有
人自然会关心这样一个问题:伪素数的个数到底有多少?换句话说,如果我只计算2^(n-1) mod
n的值,事先不准备伪素数表,那么素性判断出错的概率有多少?研究这个问题是很有价值的,毕竟我们是OIer,不可能背一个长度上千的常量数组带上考场。
统计表明,在前10亿个自然数中共有50847534个素数,而满足2^(n-1) mod n =
1的合数n有5597个。这样算下来,算法出错的可能性约为0.00011。这个概率太高了,如果想免去建立伪素数表的工作,我们需要改进素性判断的算
法。
最简单的想法就是,我们刚才只考虑了a=2的情况。对于式子a^(n-1) mod
n,取不同的a可能导致不同的结果。一个合数可能在a=2时通过了测试,但a=3时的计算结果却排除了素数的可能。于是,人们扩展了伪素数的定义,称满足
a^(n-1) mod n = 1的合数n叫做以a为底的伪素数(pseudoprime to base
a)。前10亿个自然数中同时以2和3为底的伪素数只有1272个,这个数目不到刚才的1/4。这告诉我们如果同时验证a=2和a=3两种情况,算法出错
的概率降到了0.000025。容易想到,选择用来测试的a越多,算法越准确。通常我们的做法是,随机选择若干个小于待测数的正整数作为底数a进行若干次
测试,只要有一次没有通过测试就立即把这个数扔回合数的世界。这就是Fermat素性测试。
人们自然会想,如果考虑了所有小于n的底数a,出错的概率是否就可以降到0呢?没想
到的是,居然就有这样的合数,它可以通过所有a的测试(这个说法不准确,详见我在地核楼层的回复)。Carmichael第一个发现这样极端的伪素数,他
把它们称作Carmichael数。你一定会以为这样的数一定很大。错。第一个Carmichael数小得惊人,仅仅是一个三位数,561。前10亿个自
然数中Carmichael数也有600个之多。Carmichael数的存在说明,我们还需要继续加强素性判断的算法。
Miller和Rabin两个人的工作让Fermat素性测试迈出了革命性的一步,建立了传说中的Miller-Rabin素性测试算法。 新的测试基于下面的定理:如果p是素数,x是小于p的正整数,且x^2 mod p = 1,那么要么x=1,要么x=p-1。这是显然的,因为x^2 mod p = 1相当于p能整除x^2-1,也即p能整除(x+1)(x-1)。由于p是素数,那么只可能是x-1能被p整除(此时x=1)或x+1能被p整除(此时 x=p-1)。
我们下面来演示一下上面的定理如何应用在Fermat素性测试上。前面说过341可以通过以2为底的Fermat测试,因
为2^340 mod 341=1。如果341真是素数的话,那么2^170 mod 341只可能是1或340;当算得2^170 mod
341确实等于1时,我们可以继续查看2^85除以341的结果。我们发现,2^85 mod
341=32,这一结果摘掉了341头上的素数皇冠,面具后面真实的嘴脸显现了出来,想假扮素数和我的素MM交往的企图暴露了出来。
这就
是Miller-Rabin素性测试的方法。不断地提取指数n-1中的因子2,把n-1表示成d*2^r(其中d是一个奇数)。那么我们需要计算的东西就
变成了a的d*2^r次方除以n的余数。于是,a^(d * 2^(r-1))要么等于1,要么等于n-1。如果a^(d *
2^(r-1))等于1,定理继续适用于a^(d * 2^(r-2)),这样不断开方开下去,直到对于某个i满足a^(d * 2^i) mod n =
n-1或者最后指数中的2用完了得到的a^d mod n=1或n-1。这样,Fermat小定理加强为如下形式:
尽可能提取因子2,
把n-1表示成d*2^r,如果n是一个素数,那么或者a^d mod n=1,或者存在某个i使得a^(d*2^i) mod n=n-1 (
0<=i<r ) (注意i可以等于0,这就把a^d mod n=n-1的情况统一到后面去了)
Miller-Rabin
素性测试同样是不确定算法,我们把可以通过以a为底的Miller-Rabin测试的合数称作以a为底的强伪素数(strong
pseudoprime)。第一个以2为底的强伪素数为2047。第一个以2和3为底的强伪素数则大到1 373 653。
Miller-
Rabin算法的代码也非常简单:计算d和r的值(可以用位运算加速),然后二分计算a^d mod
n的值,最后把它平方r次。程序的代码比想像中的更简单,我写一份放在下边。虽然我已经转C了,但我相信还有很多人看不懂C语言。我再写一次Pascal
吧。函数IsPrime返回对于特定的底数a,n是否是能通过测试。如果函数返回False,那说明n不是素数;如果函数返回True,那么n极有可能是
素数。注意这个代码的数据范围限制在longint,你很可能需要把它们改成int64或高精度计算(java)。
#include <iostream>
using namespace std ;
#define rd(x) (rand()%(x))
typedef unsigned long long ll; ll pow_mod(ll a,ll b,ll r)
{
ll ans=,buff=a;
while(b)
{
if(b&)
ans=(ans*buff)%r;
buff=(buff*buff)%r;
b>>=;
}
return ans;
} bool test(ll n,ll a,ll d)
{
if(n==) return true;
if(n==a) return false;
if(!(n&)) return false;
while(!(d&)) d>>=;
ll t = pow_mod(a,d,n);
while(d!=n-&&t!=n-&&t!=){
t = t*t%n;//下面介绍防止溢出的办法,对应数据量为10^18次方;
d<<=;
}
return t == n-||(d&)==;//要么t能变成n-1,要么一开始t就等于1
} bool isprime(ll n)
{
int a[] = {,,,};//或者自己生成[2,N-1]以内的随机数rand()%(n-2)+2
for(int i = ; i <= ; ++i){
if(n==a[i]) return true;
if(!test(n,a[i],n-)) return false;
}
return true;
}
int main()
{
int t,ans=;
ll N;
for(cin >> t;t;t--){
cin >> N;
cout << ((isprime(N))?"Yes":"No") <<endl;
}
}
对
于大数的素性判断,目前Miller-Rabin算法应用最广泛。一般底数仍然是随机选取,但当待测数不太大时,选择测试底数就有一些技巧了。比如,如果
被测数小于4 759 123 141,那么只需要测试三个底数2,
7和61就足够了。当然,你测试的越多,正确的范围肯定也越大。如果你每次都用前7个素数(2, 3, 5, 7, 11,
13和17)进行测试,所有不超过341 550 071 728 320的数都是正确的。如果选用2, 3, 7,
61和24251作为底数,那么10^16内唯一的强伪素数为46 856 248 255
981。这样的一些结论使得Miller-Rabin算法在OI中非常实用。通常认为,Miller-Rabin素性测试的正确率可以令人接受,随机选取
k个底数进行测试算法的失误率大概为4^(-k)。
后记:
注意上述算法中的幂运算是longlong类型,longlong×longlong肯定会出现溢出现象,如果不会java大整数,手里也没有大整数乘法的模板
有一个小技巧可以避免溢出,方法就是乘法改为加法,把上面的代码:
ll pow_mod(ll a,ll b,ll r)
{
ll ans=,buff=a;
while(b)
{
if(b&)
ans=(ans*buff)%r;
buff=(buff*buff)%r;
b>>=;
}
return ans;
}
改为:
long long mod_mul(long long a, long long b, long long n) {
long long res = ;
while (b) {
if(b & )
res = (res + a) % n;
a = (a + a) % n;
b >>= ;
}
return res;
}
long long pow_mod(long long a, long long b, long long n) {
long long res = ;
while(b) {
if(b & )
res = mod_mul(res, a, n);
a = mod_mul(a, a, n);
b >>= ;
}
return res;
}
就可以避免大整数,当然,复杂度也相应提高了一些。
素数与素性测试(Miller-Rabin测试)的更多相关文章
- 与数论的厮守01:素数的测试——Miller Rabin
看一个数是否为质数,我们通常会用那个O(√N)的算法来做,那个算法叫试除法.然而当这个数非常大的时候,这个高增长率的时间复杂度就不够这个数跑了. 为了解决这个问题,我们先来看看费马小定理:若n为素数, ...
- 关于素数:求不超过n的素数,素数的判定(Miller Rabin 测试)
关于素数的基本介绍请参考百度百科here和维基百科here的介绍 首先介绍几条关于素数的基本定理: 定理1:如果n不是素数,则n至少有一个( 1, sqrt(n) ]范围内的的因子 定理2:如果n不是 ...
- POJ1811_Prime Test【Miller Rabin素数测试】【Pollar Rho整数分解】
Prime Test Time Limit: 6000MS Memory Limit: 65536K Total Submissions: 29193 Accepted: 7392 Case Time ...
- HDU1164_Eddy'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 ...
- Miller Rabin 大素数测试
PS:本人第一次写随笔,写的不好请见谅. 接触MillerRabin算法大概是一年前,看到这个算法首先得为它的神奇之处大为赞叹,竟然可以通过几次随机数据的猜测就能判断出这数是否是素数,虽然说是有误差率 ...
- 【数论基础】素数判定和Miller Rabin算法
判断正整数p是否是素数 方法一 朴素的判定
- POJ2429_GCD & LCM Inverse【Miller Rabin素数測试】【Pollar Rho整数分解】
GCD & LCM Inverse Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 9756Accepted: 1819 ...
- POJ1811_Prime Test【Miller Rabin素数測试】【Pollar Rho整数分解】
Prime Test Time Limit: 6000MS Memory Limit: 65536K Total Submissions: 29193 Accepted: 7392 Case Time ...
- (Miller Rabin算法)判断一个数是否为素数
1.约定 x%y为x取模y,即x除以y所得的余数,当x<y时,x%y=x,所有取模的运算对象都为整数. x^y表示x的y次方.乘方运算的优先级高于乘除和取模,加减的优先级最低. 见到x^y/z这 ...
- Miller Rabin素数检测与Pollard Rho算法
一些前置知识可以看一下我的联赛前数学知识 如何判断一个数是否为质数 方法一:试除法 扫描\(2\sim \sqrt{n}\)之间的所有整数,依次检查它们能否整除\(n\),若都不能整除,则\(n\)是 ...
随机推荐
- sql为了实现转换的行列
全名 学科 成绩 牛芬 语文 81 牛芬 数学 88 牛芬 英语 84 张三 语文 90 张三 数学 98 张三 英语 90 (表一) 现有一个表如(表一) 姓名 语文 数学 英语 牛芬 81 88 ...
- honeywell D6110开发的一个工厂仓库追溯识别
近日.接触并开发了一个用honeywell D6110 二维扫描PDA的项目,应用也比較简单. 就是货品物料编码.通过中间码相应,然后中间码再依照不同OEM品牌须要生成各种商品条码并带有流水号. 要求 ...
- 【转】大素数判断和素因子分解【miller-rabin和Pollard_rho算法】
集训队有人提到这个算法,就学习一下,如果用到可以直接贴模板,例题:POJ 1811 转自:http://www.cnblogs.com/kuangbin/archive/2012/08/19/2646 ...
- 国内使用google地图的初级使用
<!DOCTYPE html><html><head><title>Simple Map</title><meta name=&quo ...
- nginx配置学习文章
partOne 自我释义部分 我的是阿里云的ubuntu *******实际上感觉这里是基本配置,很用不到*********#定义其用户或用户组user www-data;#nginx的进程数,应当为 ...
- (转)select 1 from ... sql语句中的1代表什么意思? .
select 1 from ..., sql语句中的1代表什么意思?查出来是个什么结果? select 1 from table;与select anycol(目的表集合中的任意一行 ...
- IIS 7.5 配置伪静态
IIS 7.5 配置伪静态_win服务器_脚本之家 win7下IIS的安装和配置 图文教程详细出处参考:http://www.jb51.net/article/29787.htm http://blo ...
- hadoop2.4.1伪分布式搭建
1.准备Linux环境 1.0点击VMware快捷方式,右键打开文件所在位置 -> 双击vmnetcfg.exe -> VMnet1 host-only ->修改subnet ip ...
- 装饰(Decorator)模式
1.装饰(Decorator)模式 动态给一个对象添加一些额外的职责.就增加功能来说,装饰模式比生成子类更为灵活.Component是定义一个对象接口.可以给这些对象动态地添加职责.Concre ...
- 【转】深入理解Java内存模型(四)——volatile
volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这 ...