浅谈 BSGS(Baby-Step Giant-Step 大步小步)算法

BSGS (Baby-Step Giant-Step)算法是 Shank 发明的一个用于在 O ( p ) \mathcal O(\sqrt p) O(p ) 时间复杂度内计算离散对数的算法,要求模数为质数。扩展 BSGS 不需要模数为质数。

何为离散对数?普通的对数 log ⁡ a b = c \log_ab=c logab=c 相当于求一个 c c c 满足 a c = b a^c=b ac=b。而离散对数就是如题目所说,给定 p , b , n p,b,n p,b,n,求 b l ≡ n ( m o d p ) b^l\equiv n\pmod p bl≡n(modp)。

首先显然如果 l l l 存在则必然存在 l < p l<p l<p,因为 b p − 1 ≡ 1 ( m o d p ) b^{p-1}\equiv 1\pmod p bp−1≡1(modp),如果你还不知道费马小定理请离开,你现在不应该看这篇文章。

考虑一个显然的暴力做法:依次检查。虽然是暴力,但是我们不至于傻到每求一个 b l b^l bl 都跑一次快速幂[1](#1),显然是可以递推的。

BSGS 则利用了幂运算的性质 a b × a c = a b + c a^b\times a^c=a^{b+c} ab×ac=ab+c(在模意义下显然也成立),使用类似分块的做法,做到了根号级别的时间复杂度。

首先我们暴力求出当 l ≤ p l\le \sqrt{p} l≤p 时的所有余数,如果有 n n n 则可以直接返回答案。否则,我们可以相应地求出 p < l ≤ 2 p \sqrt{p}<l\le 2\sqrt{p} p <l≤2p 时的答案,然后判断。以此类推。

看上去并没有什么作用,但是实际上,如果 b l + p ≡ n ( m o d p ) b^{l+\sqrt{p}}\equiv n\pmod p bl+p ≡n(modp),那么 b l ≡ n × ( b p ) − 1 ( m o d p ) b^{l}\equiv n\times(b^{\sqrt{p}})^{-1}\pmod p bl≡n×(bp )−1(modp)。同时 b − p b^{-\sqrt p} b−p 也是一个常数可以预处理,我们就可以利用我们求的所有当 l ≤ p l\le \sqrt p l≤p 时的余数快速判断了(把 l ≤ p l\le \sqrt p l≤p 的所有余数存到一个 set 或者哈希表 unordered_set 或者其它奇奇怪怪的数据结构中)。

进一步地,如果 b l + k p ≡ n ( m o d p ) b^{l+k\sqrt p}\equiv n\pmod p bl+kp ≡n(modp),则 b l ≡ n × ( b − p ) k ( m o d p ) b^l\equiv n\times (b^{-\sqrt p})^k\pmod p bl≡n×(b−p )k(modp)。所以我们就得到了总时间复杂度为 O ( n ) \mathcal O(\sqrt n) O(n ) 的计算离散对数的做法。

我们上面都假设 p \sqrt p p 是整数,实际上不可能是,但是我们取 p \sqrt p p 在正确性上并没有什么实际意义,只是为了保证时间复杂度,所以取 ⌊ p ⌋ \lfloor \sqrt p \rfloor ⌊p ⌋ 和 ⌈ p ⌉ \lceil \sqrt p \rceil ⌈p ⌉ 也行。

注意本题不仅仅要判断存在性,还要输出解。所以我们不能只保存余数,还要保存下标。

附上丑陋的代码。

cpp 复制代码
#include <cstdio>
#include <unordered_map>

using namespace std;

unordered_map<int, int> um;

long long qpow(int x, int y, int p)
{
    if(y == 0) return 1;
    if(y == 1) return x;
    long long r = qpow(x, y >> 1, p);
    r = r * r % p;
    if(y & 1) r = r * x % p;
    return r;
}

int main()
{
    int p, b, n;
    scanf("%d%d%d", &p, &b, &n);
    int sqrtp = 0;
    long long mul = 1;
    // 0 ~ sqrtp
    for(int i=0;1ll*i*i<=p;i++)
    {
        sqrtp = i;
        if(!um.count(mul = (i == 0 ? 1 : mul * b % p))) um[mul] = i;
        if(mul == n)
        {
            printf("%d\n", i);
            return 0;
        }
    }
    sqrtp++; mul = mul * b % p;
    long long invmul = qpow(mul, p - 2, p), mulll = 1;
    // i*sqrtp ~ (i+1)*sqrtp-1
    for(int i=1;1ll*sqrtp*i<=p;i++)
    {
        if(um.count((mulll = mulll * invmul % p) * n % p))
        {
            printf("%d\n", um[mulll * n % p] + sqrtp * i);
            return 0;
        }
    }
    printf("no solution\n");
    return 0;
}

record


  1. 倒是有一种底数确定的 O ( n ) \mathcal O(\sqrt n) O(n ) 预处理, Θ ( 1 ) \Theta(1) Θ(1) 查询的光速幂算法。核心思想和 BSGS 十分相似------预处理出 1 ≤ i ≤ p 1\le i\le \sqrt p 1≤i≤p 的 a i a^i ai 和 a i × ⌊ p ⌋ a^{i\times \lfloor \sqrt p\rfloor} ai×⌊p ⌋,然后用这两个凑出答案。用这个的话也不会劣化我们的暴力时间复杂度,毕竟是常数时间查询的。 ↩︎
相关推荐
郝学胜-神的一滴22 分钟前
Qt重复添加控件问题探析:现象、原理与解决方案
开发语言·数据库·c++·qt·程序人生
凌乱风雨121130 分钟前
从源码角度解析C++20新特性如何简化线程超时取消
前端·算法·c++20
阿猿收手吧!30 分钟前
【音视频】HLS 协议详细解析
c++·音视频
Jim-2ha038 分钟前
【平面几何】判断一个点是否在任意多边形的内部
算法
寻星探路38 分钟前
网络原理全景图:从通信起源到 TCP/IP 体系架构深度拆解
java·网络·c++·python·tcp/ip·http·架构
橘颂TA40 分钟前
【剑斩OFFER】算法的暴力美学——合并 k 个升序链表
算法·leetcode·牛客·结构与算法
前端小L40 分钟前
双指针专题(五):灵活的起跳——「无重复字符的最长子串」
javascript·算法·双指针与滑动窗口
爪哇部落算法小助手1 小时前
每日两题day67
c++·算法
hk11241 小时前
【BioTech/SystemArch】2026年度高可靠性医疗架构与生物遗传算法基准索引 (Benchmark Index)
算法·系统架构·数据集·生物信息学·垃圾回收
你撅嘴真丑1 小时前
短信计费 和 甲流病人初筛
数据结构·c++·算法