很早前就看到这两个算法了,但是之前没有看懂。
好吧,模拟赛遇到了,不学不行啊。
我们知道常用的最快的判断质数的办法约是O (n−−√) ( n ) 的。
Miller-Rabin测试是一种随机的算法,可以通过多搞几次来提高正确率。
正确率大概是 1−14c 1 − 1 4 c ,其中c是随机的次数。
Miller-Rabin测试结合了费马小定理和二次剩余定理。
费马小定理: ap−1=1 a p − 1 = 1 (p质数)
二次剩余定理:对于一质数p,若有 x2=1(mod p) x 2 = 1 ( m o d p ) ,则 x=1 x = 1 或 x=p−1 x = p − 1 。
费马小定理的证明在这里不再展开了。
至于二次剩余定理的证明,用一下逆元的知识就行了。
注意费马小定理的逆定理和二次剩余定理的逆定理都是不对的,如果我们用这两个定理去判定质数,有一定的概率无论如何都是错的,但是我们可以应该忽略这个东西吧。
流程:
把n-1分解为 s∗2t(s mod 2=1) s ∗ 2 t ( s m o d 2 = 1 )
随机一个 x(x∈[1..n−1]) x ( x ∈ [ 1.. n − 1 ] )
每次取 x′=(x∗x) mod n x ′ = ( x ∗ x ) m o d n
如果x’=1,且x≠1且x≠n-1,return false
x=x’
重复以上过程t次。
此时的 x=s∗2t=n−1 x = s ∗ 2 t = n − 1 ,如果x≠1,return false
随机x一个合适的次数。
return true
裸题推荐:51nod 1186 质数检测 V2
代码放到博客最后面。
据证明,x取前9个质数可以准确判断2^63-1以内的数是不是质数。
首先你得会一个高效的质数判定算法。
这个分解质因数的算法也是随机的,复杂度大概是 O(n1/4) O ( n 1 / 4 )
一个没有什么用的东西。
比如说你从1000个数里面挑选一个数,它是42的概率是1/1000
但是如果你选取两个数x1,x2的话,abs(x1-x2)=42的概率就比较高了。
其实这样做的概率随选的数变大而变小,由于42比较小,所以概率变大了,如果选1000概率反而变小了。
回到原题,一个n的因数肯定更靠近1而不是n,这个你从一个数大于 sqrtn s q r t n 的质因子不会超过一个就可以感性认识的。
但是实际上就算用了生日悖论这个概率似乎也就提高了1倍。
直接取因数不行,那么取gcd行不行?
答案当然是肯定的,gcd(abs(x1-x2),n)>1的概率显然要大的多,这样我们就找到了一个因数。
选定一个种子x,每次 x′=(x∗x+a)(mod n) x ′ = ( x ∗ x + a ) ( m o d n )
我们发现上面的那种方法会造出一个环来,但是环的长度是不知道的,如何快速判断环。
hash?慢
暴力循环10^6次?还是慢
floyd判圈法,妙不可言,详情见标。
如果一个数已经是质数,你分解个头啊,怎么分解都不行好吗?
推荐裸题:hdu3864 D_num
51nod那题可以用__int128解决(考试时别用,除非说了)。
Code(51nod 1186):
#include
#define dint __int128
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y ; i ++)
using namespace std;
int rand(int x, int y) {return ((ll) RAND_MAX * rand() + rand()) % (y - x + 1) + x;}
void scan(dint &x) {
char c = ' '; for(;c < '0' || c > '9'; c = getchar());
for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - 48;
}
dint mul(dint a, dint b, dint mo) {
dint s = 0;
for(; b; b /= 2, a = (a + a) % mo)
if(b % 2) s = (s + a) % mo;
return s;
}
dint ksm(dint x, dint y, dint mo) {
dint s = 1;
for(; y; y /= 2, x = mul(x, x, mo))
if(y % 2) s = mul(s, x, mo);
return s;
}
dint n;
int pd(dint n) {
if(n == 2) return 1;
if(n < 2 || n % 2 == 0) return 0;
dint u = n - 1, t = 0;
while(u % 2 == 0) u /= 2, t ++;
fo(ii, 1, 40) {
dint x = rand() % (n - 1) + 1;
x = ksm(x, u, n);
fo(i, 1, t) {
dint y = mul(x, x, n);
if(y == 1 && x != 1 && x != n - 1) return 0;
x = y;
}
if(x != 1) return 0;
}
return 1;
}
int main() {
srand((unsigned) time (NULL));
scan(n);
if(pd(n)) printf("Yes"); else printf("No");
}
Code(hdu3864):
#include
#define ll long long
#define ld long double
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;
int T; ll n;
ll mul(ll x, ll y, ll mo) {
x %= mo; y %= mo;
ll z = (ld) x * y / mo; z = x * y - z * mo;
if(z < 0) z += mo; else if(z > mo) z -= mo;
return z;
}
ll ksm(ll x, ll y, ll mo) {
ll s = 1;
for(; y; y /= 2, x = mul(x, x, mo))
if(y & 1) s = mul(s, x, mo);
return s;
}
int mr(ll n) {
if(n == 2) return 1;
if(n < 2 || n % 2 == 0) return 0;
ll s = n - 1, t = 0;
while(s % 2 == 0) s /= 2, t ++;
fo(ii, 1, 20) {
ll x = ksm(rand() % (n - 1) + 1, s, n);
fo(i, 1, t) {
ll y = mul(x, x, n);
if(y == 1 && x != 1 && x != n - 1) return 0;
x = y;
}
if(x != 1) return 0;
}
return 1;
}
ll gcd(ll x, ll y) {return !y ? x : gcd(y, x % y);}
ll fen(ll n, ll c) {
ll i = 1, k = 2, x = rand() % n, y = x;
while(1) {
i ++; x = (mul(x, x, n) + c) % n;
ll d = gcd(abs(x - y), n);
if(d != 1 && d != n) return d;
if(x == y) return n;
if(i == k) y = x, k <<= 1;
}
}
ll u[1000], v[1000];
void find(ll n) {
if(n == 1) return;
if(mr(n)) u[++ u[0]] = n; else {
ll p = fen(n, rand() % n); find(p); find(n / p);
}
}
int main() {
srand((unsigned) time (NULL));
while(scanf("%lld", &n) != EOF) {
u[0] = 0; find(n);
sort(u + 1, u + u[0] + 1);
if(u[0] == 3 && u[1] == u[2] && u[2] == u[3]) {
printf("%lld %lld %lld\n", u[1], u[1] * u[1], u[1] * u[1] * u[1]);
continue;
}
if(u[0] == 2 && u[1] != u[2]) {
printf("%lld %lld %lld\n", u[1], u[2], n);
continue;
}
printf("is not a D_num\n");
}
}