@TOC
素性测试是检验一个给定的整数是否为素数的测试。
最简单的就是用 n\sqrt{n}n 以内的数去试除。这是确定性的算法,即能准确知道 nnn 是否为质数。
但今天学习的是一种随机算法。
Fermat 小定理
如果 ppp 是一个质数,且 a%p≠0a\%p≠0a%p=0,则有 ap−1≡1(modp)a^{p-1}\equiv 1\pmod pap−1≡1(modp)
利用 Fermat定理
可以得到一个测试合数的有力算法:对 n>1n>1n>1,选择 a>1a>1a>1, 计算 an−1modna^{n-1} \mod nan−1modn。
-
若结果 ≠1\neq 1=1,则 nnn 是合数。
-
若结果 =1=1=1, 则 nnn 可能是素数,并被称为一个以 aaa 为基的弱可能素数 (
a-PRP
) 。若 nnn 是合数,则又被称为一个以 aaa 为基的伪素数。
这个算法的成功率是相当高的。在 <25000000000<25000000000<25000000000 的 109198740510919874051091987405 个素数中,一共只有 218532185321853 个以 222 为基的伪素数。
但不幸的是,存在无穷多个被称为 Carmichael数
(卡迈克尔数)的整数,对于任意与其互素的整数 aaa 算法的计算结果都是 111。
最小的五个 Carmichael数
是 561、1105、1729、2465、2801561、1105、1729、2465、2801561、1105、1729、2465、2801 。
miller_rabin(米勒-拉宾)素性测试
定理
:如果 ppp 是个 >2>2>2 的质数,则方程 x2≡1(modp)x^2\equiv 1\pmod px2≡1(modp) 的解只有 x=±1x=±1x=±1。
证明:
x2≡1(modp)⇒x2−1≡0(modp)⇒(x+1)(x−1)≡0(modp)x^2\equiv 1\pmod p\Rightarrow x^2-1\equiv 0\pmod p\Rightarrow (x+1)(x-1)\equiv 0\pmod px2≡1(modp)⇒x2−1≡0(modp)⇒(x+1)(x−1)≡0(modp)
若 p∣(x+1)∧p∣(x−1)p|(x+1)\wedge p|(x-1)p∣(x+1)∧p∣(x−1)。则一定存在两个数 j,kj,kj,k,使得 x+1=jp,x−1=kpx+1=jp,x-1=kpx+1=jp,x−1=kp,两式相减 2=(k−j)p2=(k-j)p2=(k−j)p,不可能。
所以只可能是 p∣(x+1)∨p∣(x−1)p|(x+1)\vee p|(x-1)p∣(x+1)∨p∣(x−1)。
设 p∣(x+1)p|(x+1)p∣(x+1),则 x+1=kp⇒x≡−1(modp)x+1=kp\Rightarrow x\equiv -1\pmod px+1=kp⇒x≡−1(modp)
设 p∣(x−1)p|(x-1)p∣(x−1),则 x−1=kp⇒x≡1(modp)x-1=kp\Rightarrow x\equiv 1\pmod px−1=kp⇒x≡1(modp)
以上定理的逆否命题:当方程 x2≡1(modp)x^2\equiv 1\pmod px2≡1(modp) 有一个解 x≠−1∧x≠1x\neq-1\wedge x\neq 1x=−1∧x=1,则 ppp 一定不是质数。
miller_rabin
是一个质数判断算法,采用随机算法能够在很短的时间里判断一个数是否是质数.
如何将卡迈克尔数甄别出来,这里要用到 二次探测方法。
算法流程:
-
设 ppp 是要判断的数,222 特判后,ppp 如果是质数必须是奇数
-
将 p−1p-1p−1 分解为 2r∗k2^r*k2r∗k,则有 ap−1=(ak)2ra^{p-1}=(a^k)^{2^r}ap−1=(ak)2r
-
可以先求出 aka^kak,然后将其不断平方,并取模 ppp
-
对于任意的 0<a<p0<a<p0<a<p,有 ak≡1(modp)a^k\equiv 1\pmod pak≡1(modp),或者 a2s∗k≡−1(modp),0≤s<ra^{2^s*k}\equiv-1\pmod p,0\le s<ra2s∗k≡−1(modp),0≤s<r。
只要有一个等式成立,那么后面不断平方结果也是 111,在代码实现时就会立即跳出。
如果一个都没有成立则说明一定不是个质数。
但是当 ppp 为合数的时候,也可能会骗过检测,这就是“伪素数”。
如果挑选多个不同的 aaa 来检测,那么就会降低骗过的概率。
这也就是 miller_rabin
要多次操作的原因。
容错率是 14c\frac{1}{4}^c41c,取决于操作次数 ccc。
在竞赛中,用固定的几个数就够了,附表。
n≤n≤n≤ | aaa |
---|---|
2047 | 2 |
1373653 | 2,3 |
9080191 | 31,73 |
25326001 | 2,3,5 |
4759123141 | 2,7,61 |
1122004669633 | 2,3,13,1662803 |
2152302898747 | 2,5,7,11 |
3474749660383 | 2,5,7,11,13 |
341550071728321 | 2,3,5,7,11,13,17 |
3825123056546413051 | 2,3,5,7,11,13,17,19 |
HDU2138
#include <cstdio>
using namespace std;
#define int long long
int test[5] = { 2, 3, 61 };int qkpow( int x, int y, int mod ) {int ans = 1;while( y ) {if( y & 1 ) ans = ans * x % mod;x = x * x % mod;y >>= 1;}return ans;
}bool dfs( int n, int a, int d ) {if( n == a ) return 1;if( ! ( n & 1 ) ) return 0;while( ! ( d & 1 ) ) d >>= 1;int x = qkpow( a, d, n );while( d ^ ( n - 1 ) and x ^ 1 and x ^ ( n - 1 ) ) {x = x * x % n;d <<= 1;}return x == n - 1 or ( d & 1 );
}bool isprime( int n ) {if( n == 2 ) return 1;for( int i = 0;i < 2;i ++ ) if( ! dfs( n, test[i], n - 1 ) ) return 0;return 1;
}signed main() {int T, n;while( ~ scanf( "%lld", &T ) ) {int sum = 0;for( int i = 1;i <= T;i ++ ) {scanf( "%lld", &n );if( isprime( n ) ) sum ++;}printf( "%lld\n", sum );}return 0;
}