好的,我来给你系统梳理一下 BSGS 算法(Baby-Step Giant-Step,常用来解离散对数问题),结合你贴的解释。


1. 问题背景

离散对数问题的形式是:

$ A^x \equiv B \pmod{C} $

其中:

  • $ A $ 是底数(与模数 $ C $ 互质),
  • $ B $ 是目标值,
  • $ C $ 是模数,通常是质数,
  • 要求解未知的指数 $ x $。

这是密码学里很多问题的核心难点(比如 Diffie-Hellman、ElGamal)。


2. 思路拆分:平方分割

直接暴力枚举 $ x $ 要花 $ O(C) $ 时间,太大。

BSGS 的核心思想:
把指数 $ x $ 分成两部分:

$ x = i \cdot m - j $

其中:

  • $ m = \lceil \sqrt{C} \rceil $,
  • $ i, j $ 都在 $ 0 \sim m $ 的范围内。

这样就把大问题转化成两个“较小区间”的枚举。


3. 推导过程

把原式:

$ A^x \equiv B \pmod{C} $

代入 $ x = i \cdot m - j $:

$ A^{i m - j} \equiv B \pmod{C} $

化简为:

$ A^{i m} \equiv A^j \cdot B \pmod{C} $


4. 算法步骤

  1. 预处理(Baby Step)
    遍历 $ j = 0 \dots m $,计算:

$ A^j \cdot B \pmod{C} $

存入哈希表(key = 结果,value = j)。

  1. 大步枚举(Giant Step)
    遍历 $ i = 1 \dots m $,计算:

$ A^{i \cdot m} \pmod{C} $

然后看它是否在哈希表中出现。

  • 如果出现,设匹配时的 $ j $ 为哈希表中的值,则:

$ x = i \cdot m - j
$

即为所求解。


5. 时间复杂度

  • Baby Step:计算 $ m \approx \sqrt{C} $ 次,存入哈希表。
  • Giant Step:也只需计算 $ m $ 次,每次查表 O(1)。

所以整体复杂度:

$ O(\sqrt{C}) $

这比暴力 O(C) 快得多,是 子指数级别


6. 你提到的细节

  • 有的版本用 $ x = i \cdot m + j $。
    • 这样需要算逆元,因为要把公式变形为 $ A^{i m} \cdot A^j = B $。
    • 所以更复杂。
  • 你看到的这种写法 $ x = i m - j $,
    • 避免了求逆元,
    • 逻辑更直观,常用于实际实现。

7. 为什么只要算到 $ m = \lceil \sqrt{C} \rceil $?#我去这个是上取整符

因为指数的取值范围最大是 $ C $,
把它拆分成两段 $ \sqrt{C} $,总覆盖范围是:

$ i \cdot m - j \quad (i, j \leq m) $

这能保证所有 $ 0 \leq x < C $ 的情况都能被覆盖到。
再结合 费马小定理(如果 $ C $ 是质数,则 $ A^{C-1} \equiv 1 \pmod C $),
即指数超过 $ C-1 $ 时,可以对 $ C-1 $ 取模,因此不需要再枚举更大范围。


8. 总结

  • 问题:解 $ A^x \equiv B \pmod C $。
  • 思想:平方分割,把 $ x $ 拆成两部分。
  • 步骤
    1. 预处理 $ A^j \cdot B $,存表。
    2. 枚举 $ A^{i m} $,查表。
    3. 找到匹配时得到解。
  • 复杂度:$ O(\sqrt{C}) $。
  • 关键点:选用 $ x = i m - j $ 可以避免逆元。

示例代码

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
a = 13
c = 114514
P = 100000000000099

import math

def bsgs(a, c, P):
# 确定互素
assert math.gcd(a, P) == 1
m = math.ceil(math.sqrt(P))
step_dict = {} # 字典

# right_val = a^j*c % P
right_val = c % P # 计算幂次为0时的c对P取模的值
for j in range(m + 1):
step_dict[right_val] = j # 构建字典
right_val = right_val * a % P # 累乘

# left_val = a^im%p
am = pow(a, m, P)
for i in range(1, m + 1):
left_val = pow(am, i, P)
if left_val in step_dict: # 找到了左右两边同余的i和j
j = step_dict[left_val]
return m * i - j
return None # 无解

x = bsgs(a, c, P)
assert pow(a, x, P) == c % P
print(x)

顺带学一下字典:

1
2
3
4
5
6
7
8
9
10
11
# 创建一个空字典
student_scores = {}

# 往字典里加内容:键是名字,值是分数
student_scores["小明"] = 95
student_scores["小红"] = 88
student_scores["小刚"] = 76

print(student_scores)

#{'小明': 95, '小红': 88, '小刚': 76}
1
2
print(student_scores["小明"])   # 95
print(student_scores["小红"]) # 88
1
2
3
4
5
6
7
8
for name, score in student_scores.items():
print(name, "的分数是", score)

'''
小明 的分数是 95
小红 的分数是 88
小刚 的分数是 76
'''