【学习笔记】Miller–Rabin素数测试

【算法简介】

  • MillerRabin M i l l e r R a b i n 素数测试是一种判断一个数是否是质数的方式。
  • 其单次测试的时间复杂度不会超过 O(Log2N) O ( L o g 2 N ) ,期望为 O(LogN) O ( L o g N ) ,几乎不需要额外的空间。
  • MillerRabin M i l l e r R a b i n 素数测试不是一个确定算法,其单次测试有不超过 14 1 4 的概率会将一个合数误判为一个素数。但当被测试数在某一个范围内时,我们可以通过选取适当的测试底数让 MillerRabin M i l l e r R a b i n 素数测试对于这个范围内的每一个数都能够正确地得出结果。

【算法流程】

  • 一个质数具有许多合数不具有的性质,例如:

  • 定理 1 1 :若 p p 是质数,则对于任意 0<a<p 0 < a < p ,有 ap11 (mod  p) a p − 1 ≡ 1   ( m o d     p )

  • 引理 2 2 :若 p p 是质数,且 x21 (mod  p) x 2 ≡ 1   ( m o d     p ) ,那么 x1 (mod  p) x ≡ 1   ( m o d     p ) xp1 (mod  p) x ≡ p − 1   ( m o d     p ) 中的一个成立。

  • 其中定理 1 1 就是 费马小定理 ,引理 2 2 的证明的如下:

    由于 x21 (mod  p) x 2 ≡ 1   ( m o d     p ) ,有 (x1)(x+1)0 (mod  p) ( x − 1 ) ( x + 1 ) ≡ 0   ( m o d     p )

    (x1)(x+1) ( x − 1 ) ( x + 1 ) p p 的倍数,因此引理 2 2 得证。

  • 那么,一个简单的想法就是我们对于被测试数 N N ,分别测试定理 1 1 和引理 2 2 是否成立,若发现不成立,则说明被测试数 N N 一定是一个合数。

  • 具体而言,我们首先要选取一个底数 a(a<N) a ( a < N ) ,计算 aN1 mod  N a N − 1   m o d     N ,若 aN1 mod  N1 a N − 1   m o d     N ≠ 1 ,那么 N N 一定是一个合数。

    否则,即 aN11 (mod  N) a N − 1 ≡ 1   ( m o d     N ) ,若此时 N1 N − 1 为偶数,我们还可以计算 aN12 mod  N a N − 1 2   m o d     N , 若 aN12 mod  N1 a N − 1 2   m o d     N ≠ 1 aN12 mod  NN1 a N − 1 2   m o d     N ≠ N − 1 ,那么 N N 一定是一个合数。

    否则,若 aN121 (mod  N) a N − 1 2 ≡ 1   ( m o d     N ) ,并且此时 N12 N − 1 2 为偶数,我们还可以计算 aN14 mod  N a N − 1 4   m o d     N , 若 aN14 mod  N1 a N − 1 4   m o d     N ≠ 1 aN14 mod  NN1 a N − 1 4   m o d     N ≠ N − 1 ,那么 N N 一定是一个合数。

    如此重复,直到 N N 被确定是一个合数,或者 axN1 (mod  N) a x ≡ N − 1   ( m o d     N ) ,或者 x x 为奇数为止。可以发现,这个过程最多会进行 O(LogN) O ( L o g N ) 次,每一次我们需要计算一个乘幂,因此单次测试的最坏时间复杂度为 O(Log2N) O ( L o g 2 N )

  • 可以发现,一些合数同样能够通过以某些 a a 为底的 MillerRabin M i l l e r R a b i n 素数测试,一种可行的选择是随机底数 a a ,进行 k k MillerRabin M i l l e r R a b i n 素数测试,算法的错误几率将小于 14k 1 4 k

  • 前面提到,当被测试数在某一个范围内时,我们可以通过选取适当的测试底数让 MillerRabin M i l l e r R a b i n 素数测试对于这个范围内的每一个数都能够正确地得出结果。下面是节选自 维基百科 的选取 a a 的方式。

  • N<4,759,123,141 N < 4 , 759 , 123 , 141 ,选取 a=2,7,61 a = 2 , 7 , 61 即可确保算法得出正确结果。

  • N<3,825,123,056,546,413,05131018 N < 3 , 825 , 123 , 056 , 546 , 413 , 051 ≈ 3 ∗ 10 18 ,选取 a=2,3,5,7,11,13,17,19,23 a = 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 即可确保算法得出正确结果。

  • N<18,446,744,073,709,551,616=264 N < 18 , 446 , 744 , 073 , 709 , 551 , 616 = 2 64 ,选取 a=2,3,5,7,11,13,17,19,23,29,31,37 a = 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 , 31 , 37 即可确保算法得出正确结果。

【代码】

  • 模板题【LOJ143】

#include

using namespace std;
const int MAXN = 100005;
const int p[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
typedef long long ll;
typedef long double ld;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
  x = 0; int f = 1;
  char c = getchar();
  for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
  for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
  x *= f;
}
template <typename T> void write(T x) {
  if (x < 0) x = -x, putchar('-');
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
  write(x);
  puts("");
}
ll times(ll a, ll b, ll P) {
  ll tmp = (ld) a * b / P;
  return ((a * b - tmp * P) % P + P) % P;
}
ll power(ll a, ll b, ll P) {
  if (b == 0) return 1;
  ll tmp = power(a, b / 2, P);
  if (b % 2 == 0) return times(tmp, tmp, P);
  else return times(a, times(tmp, tmp, P), P);
}
bool prime(ll n) {
  for (int i = 0; i <= 8; i++) {
      if (p[i] == n) return true;
      else if (p[i] > n) return false;
      ll tmp = power(p[i], n - 1, n), tnp = n - 1;
      if (tmp != 1) return false;
      while (tmp == 1 && tnp % 2 == 0) {
          tnp /= 2;
          tmp = power(p[i], tnp, n);
          if (tmp != 1 && tmp != n - 1) return false;
      }
  }
  return true;
}
int main() {
  ll n;
  while (scanf("%lld", &n) != EOF) {
      if (n == 1) {
          puts("N");
          continue;
      }
      if (prime(n)) puts("Y");
      else puts("N");
  }
  return 0;
}

你可能感兴趣的:(【类型】学习笔记,【算法】数学)