离散对数
离散对数问题
一、从普通对数开始
如果有一个方程:\(2^x = 8\),可以很快求出 \(x = \log_2 8 = 3\)。这里的 \(\log\) 就是对数运算。
二、加上“模运算”的限制
现在,把这个问题搬到模运算上,问题就变成了这样:给定整数 \(a,b\) 和一个质数 \(p\),求解 \(x\) 满足:\(a^x \equiv b \pmod{p}\)。
要找的这个指数 \(x\),就叫做“\(b\) 以 \(a\) 为底,模 \(p\) 的离散对数”。
为什么叫“离散”?因为是在一个有限的、不连续的整数集合 \(\{ 0,1,2, \dots, p-1 \}\) 上寻找解,而不是在连续的实数上。
为什么它很难?因为模运算的性质,\(a^x\) 的值在模 \(p\) 的意义下会“跳跃”,没有像实数上那样平滑的单调性。不能直接用 \(\log\) 函数来求解。
三、一个简单的例子
求解:\(3^x \equiv 13 \pmod{17}\)
- \(a=3\)(底数)
- \(b=13\)(结果)
- \(p=17\)(模数)
最直接、最暴力的方法就是枚举 \(x\):
- 当 \(x=0\) 时,\(3^0=1 \equiv 1 \pmod{17}\)
- 当 \(x=1\) 时,\(3^1=3 \equiv 3 \pmod{17}\)
- 当 \(x=2\) 时,\(3^2=9 \equiv 9 \pmod{17}\)
- 当 \(x=3\) 时,\(3^3=27 \equiv 10 \pmod{17}\)
- 当 \(x=4\) 时,\(3^4=81 \equiv 13 \pmod{17}\),找到了
所以,这个问题的解是 \(x=4\)。
但如果 \(p\) 是一个非常大的质数,暴力枚举就会花费太长的时间。因此,需要一个更高效的算法。
BSGS 算法
BSGS(Baby-Step Giant-Step)算法是解决离散对数问题的经典方法。它的核心思想是 meet in the middle,用空间换时间,将暴力枚举的 \(O(p)\) 复杂度优化到 \(O(\sqrt{p})\)。
一、核心思想:拆分指数
把未知的指数 \(x\) 拆成两部分。设定一个步长 \(m\),大小大约为 \(O(\sqrt{p})\)。那么任何 \(x\) 都可以表示为:\(x = i \cdot m - j\),其中 \(0 \le j \lt m\),\(1 \le i \le m\),这里用减法是为了后面推导方便。
- \(i \cdot m\) 就像是“巨人”迈出的大步(Giant Step)。
- \(j\) 就像是“婴儿”爬出的小步(Baby Step)。
二、推导“相遇点”
把 \(i \cdot m - j\) 代入原方程 \(a^x \equiv b \pmod{p}\),得到 \(a^{i \cdot m - j} \equiv b \pmod{p}\)。
通过移项,把“大步”和“小步”分到等式两边:\(a^{i \cdot m} \equiv b \cdot a^j \pmod{p}\)。
这就是“相遇点”方程。
- 等式左边 \(a^{i \cdot m}\):只和“巨人”走的步数 \(i\) 有关。
- 等式右边 \(b \cdot a^j\):只和“婴儿”走的步数 \(j\) 有关。
算法的思路就是:
- 婴儿先走:先计算出所有可能的右边的值,即 \(b \cdot a^j\),并把这些值和对应的 \(j\) 存入一个哈希表。
- 巨人再走:然后计算所有可能的左边的值,即 \(a^{i \cdot m}\)。每计算出一个值,就去哈希表里查一下,看这个位置婴儿是否已经到过。
- 相遇:如果查到了,说明找到了满足方程的 \(i\) 和 \(j\),那么 \(x=i \cdot m - j\) 就是解。
ll bsgs(ll a, ll b, ll p) {
b %= p; // 将 b 对 p 取模,简化计算
// 特殊情况处理
if (b == 1 && a != 0) return 0;
// 计算步长 m = ceil(sqrt(p))
ll m = (ll)(ceil(sqrt(p)));
// Baby Steps Phase: 计算 b * a^j 并存入哈希表
// 哈希表的格式是 { value -> j }
unordered_map<ll, ll> baby_steps;
ll a_j = 1;
for (ll j = 0; j < m; j++) {
ll val = b * a_j % p;
baby_steps[val] = j;
a_j = a_j * a % p;
}
// Giant Steps Phase: 计算 (a^m)^i 并查找
ll giant_step_unit = a_j; // 先计算出巨人一步的大小
ll giant_val = 1;
for (ll i = 1; i <= m; i++) {
giant_val = giant_val * giant_step_unit % p;
// 在哈希表中查找当前的 giant step
auto it = baby_steps.find(giant_val);
if (it != baby_steps.end()) {
// 找到了匹配,it->second 就是 j
ll j = it->second;
// 计算并返回最终解 x = i*m - j;
return i * m - j;
}
}
return -1; // 遍历完所有步数都找不到,无解
}
习题:P3846 [TJOI2007] 可爱的质数/【模板】BSGS
参考代码
#include <cstdio>
#include <cmath>
#include <unordered_map>
using ll = long long;
using std::unordered_map;
ll bsgs(ll a, ll b, ll p) {
b %= p;
if (b == 1 && a != 0) return 0;
ll m = (ll)(ceil(sqrt(p)));
unordered_map<ll, ll> baby_steps;
ll a_j = 1;
for (ll j = 0; j < m; j++) {
ll val = b * a_j % p;
baby_steps[val] = j;
a_j = a_j * a % p;
}
ll giant_step_unit = a_j, giant_val = 1;
for (ll i = 1; i <= m; i++) {
giant_val = giant_val * giant_step_unit % p;
auto it = baby_steps.find(giant_val);
if (it != baby_steps.end()) {
ll j = it->second;
return i * m - j;
}
}
return -1;
}
int main()
{
ll p, b, n; scanf("%lld%lld%lld", &p, &b, &n);
ll l = bsgs(b, n, p);
if (l == -1) printf("no solution\n");
else printf("%lld\n", l);
return 0;
}

浙公网安备 33010602011771号