Miller-Rabin和Pollard-Rho算法学习小记

前言:

很早前就看到这两个算法了,但是之前没有看懂。

好吧,模拟赛遇到了,不学不行啊。

Miller-Rabin测试:

我们知道常用的最快的判断质数的办法约是O n ( n ) 的。

Miller-Rabin测试是一种随机的算法,可以通过多搞几次来提高正确率。

正确率大概是 114c 1 − 1 4 c ,其中c是随机的次数。

Miller-Rabin测试结合了费马小定理和二次剩余定理。

费马小定理: ap1=1 a p − 1 = 1 (p质数)

二次剩余定理:对于一质数p,若有 x2=1(mod p) x 2 = 1 ( m o d   p ) ,则 x=1 x = 1 x=p1 x = p − 1

费马小定理的证明在这里不再展开了。

至于二次剩余定理的证明,用一下逆元的知识就行了。

注意费马小定理的逆定理和二次剩余定理的逆定理都是不对的,如果我们用这两个定理去判定质数,有一定的概率无论如何都是错的,但是我们可以应该忽略这个东西吧。

流程:
把n-1分解为 s2t(s mod 2=1) s ∗ 2 t ( s   m o d   2 = 1 )

随机一个 x(x[1..n1]) x ( x ∈ [ 1.. n − 1 ] )

每次取 x=(xx) mod n x ′ = ( x ∗ x )   m o d   n

如果x’=1,且x≠1且x≠n-1,return false

x=x’

重复以上过程t次。

此时的 x=s2t=n1 x = s ∗ 2 t = n − 1 ,如果x≠1,return false

随机x一个合适的次数。

return true

裸题推荐:51nod 1186 质数检测 V2

代码放到博客最后面。

据证明,x取前9个质数可以准确判断2^63-1以内的数是不是质数。

Pollard-Rho算法:

首先你得会一个高效的质数判定算法。

这个分解质因数的算法也是随机的,复杂度大概是 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=(xx+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");
    }
}

你可能感兴趣的:(模版,数论杂集,51nod,HDU)