离散对数

离散对数问题

一、从普通对数开始

如果有一个方程:\(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\) 有关。

算法的思路就是:

  1. 婴儿先走:先计算出所有可能的右边的值,即 \(b \cdot a^j\),并把这些值和对应的 \(j\) 存入一个哈希表。
  2. 巨人再走:然后计算所有可能的左边的值,即 \(a^{i \cdot m}\)。每计算出一个值,就去哈希表里查一下,看这个位置婴儿是否已经到过。
  3. 相遇:如果查到了,说明找到了满足方程的 \(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;
}
posted @ 2025-07-14 12:24  RonChen  阅读(119)  评论(0)    收藏  举报