好的,我来给你系统梳理一下 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. 算法步骤
- 预处理(Baby Step)
遍历 $ j = 0 \dots m $,计算:
$ A^j \cdot B \pmod{C} $
存入哈希表(key = 结果,value = j)。
- 大步枚举(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 $ 拆成两部分。
- 步骤:
- 预处理 $ A^j \cdot B $,存表。
- 枚举 $ A^{i m} $,查表。
- 找到匹配时得到解。
- 复杂度:$ O(\sqrt{C}) $。
- 关键点:选用 $ x = i m - j $ 可以避免逆元。
示例代码
1 | a = 13 |
顺带学一下字典:
1 | # 创建一个空字典 |
1 | print(student_scores["小明"]) # 95 |
1 | for name, score in student_scores.items(): |