BSGS

基础篇

BSGS(baby-step giant-step),即大步小步算法。常用于求解离散对数问题。形式化地说,该算法可以在 的时间内求解

其中 。方程的解 满足 。(在这里需要注意,只要 就行了,不要求 是素数)

算法描述

,其中 ,则有 ,稍加变换,则有

我们已知的是 ,所以我们可以先算出等式右边的 的所有取值,枚举 ,用 hash/map 存下来,然后逐一计算 ,枚举 ,寻找是否有与之相等的 ,从而我们可以得到所有的

注意到 均小于 ,所以时间复杂度为 ,用 map 则多一个

为什么要求 互质

注意到我们求出的是 ,我们需要保证从 可以推回 ,前式是后式左右两边除以 得到,所以必须有

进阶篇

求解

其中 是个质数。

该模型可以通过一系列的转化为成 基础篇 中的模型,你可能需要了解关于 阶与原根 的知识。

由于式子中的模数 是一个质数,那么 一定存在一个原根 。因此对于模 意义下的任意的数 有且仅有一个数 满足

方法一

我们令 的原根(我们一定可以找到这个 ),问题转化为求解 。稍加变换,得到

于是就转换成了我们熟知的 BSGS 的基本模型了,可以在 解出 ,这样可以得到原方程的一个特解

方法二

我们仍令 ,并且设 ,于是我们得到

方程两边同时取离散对数得到

我们可以通过 BSGS 求解 得到 ,于是这就转化成了一个线性同余方程的问题。这样也可以解出 ,求出 的一个特解

找到所有解

在知道 的情况下,我们想得到原问题的所有解。首先我们知道 ,于是可以得到

于是得到所有解为

对于上面这个式子,显然有 。因此我们设 ,得到

这就是原问题的所有解。

实现

下面的代码实现的找原根、离散对数解和原问题所有解的过程。

参考代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
int gcd(int a, int b) { return a ? gcd(b % a, a) : b; }

int powmod(int a, int b, int p) {
  int res = 1;
  while (b > 0) {
    if (b & 1) res = res * a % p;
    a = a * a % p, b >>= 1;
  }
  return res;
}

// Finds the primitive root modulo p
int generator(int p) {
  vector<int> fact;
  int phi = p - 1, n = phi;
  for (int i = 2; i * i <= n; ++i) {
    if (n % i == 0) {
      fact.push_back(i);
      while (n % i == 0) n /= i;
    }
  }
  if (n > 1) fact.push_back(n);
  for (int res = 2; res <= p; ++res) {
    bool ok = true;
    for (int factor : fact) {
      if (powmod(res, phi / factor, p) == 1) {
        ok = false;
        break;
      }
    }
    if (ok) return res;
  }
  return -1;
}

// This program finds all numbers x such that x^k=a (mod n)
int main() {
  int n, k, a;
  scanf("%d %d %d", &n, &k, &a);
  if (a == 0) return puts("1\n0"), 0;
  int g = generator(n);
  // Baby-step giant-step discrete logarithm algorithm
  int sq = (int)sqrt(n + .0) + 1;
  vector<pair<int, int>> dec(sq);
  for (int i = 1; i <= sq; ++i)
    dec[i - 1] = {powmod(g, i * sq * k % (n - 1), n), i};
  sort(dec.begin(), dec.end());
  int any_ans = -1;
  for (int i = 0; i < sq; ++i) {
    int my = powmod(g, i * k % (n - 1), n) * a % n;
    auto it = lower_bound(dec.begin(), dec.end(), make_pair(my, 0));
    if (it != dec.end() && it->first == my) {
      any_ans = it->second * sq - i;
      break;
    }
  }
  if (any_ans == -1) return puts("0"), 0;
  // Print all possible answers
  int delta = (n - 1) / gcd(k, n - 1);
  vector<int> ans;
  for (int cur = any_ans % delta; cur < n - 1; cur += delta)
    ans.push_back(powmod(g, cur, n));
  sort(ans.begin(), ans.end());
  printf("%d\n", ans.size());
  for (int answer : ans) printf("%d ", answer);
}

扩展篇

接下来我们求解

其中 不一定互质。

时,在模 意义下 存在逆元,因此可以使用 BSGS 算法求解。于是我们想办法让他们变得互质。

具体地,设 。如果 ,则原方程无解。否则我们把方程同时除以 ,得到

如果 仍不互质就再除,设 。如果 ,则方程无解;否则同时除以 得到

同理,这样不停的判断下去。直到

,于是方程就变成了这样:

由于 ,于是推出 。这样 就有逆元了,于是把它丢到方程右边,这就是一个普通的 BSGS 问题了,于是求解 后再加上 就是原方程的解啦。

注意,不排除解小于等于 的情况,所以在消因子之前做一下 枚举,直接验证 ,这样就能避免这种情况。

习题


评论