算法学习笔记(10): BSGS算法及其扩展算法

算法,学习,笔记,bsgs,及其,扩展 · 浏览次数 : 50

小编点评

**仅供参考** ** inv(i) i \\equiv 1 mod p// i * inv(i) + kp = 1 (kown i, p, 1)template<typename T>T inv(T i, T p) { T iv, k; exgcd(i, p, &iv, &k); iv %= p; // 注意点4 return iv < 0 ? iv + p : iv;}data exBSGS(data a, data b, data p) { // 注意点 1 b %= p; // 注意点 2 if (b == 1 || p == 1) return 0; data d, ak(1), k(0); while ((d = gcd(a, p)) != 1) { if (b % d) return -1; ++k, p /= d, b /= d; ak = a / d * ak % p; // 注意点 3 if (ak == b) return k; } // 注意点 4 b = b * inv(ak, p) % p; data res = BSGS(a, b, p); // 注意点 5 if (~res) return res + k; return -1;}注意点1:为什么需要一次 b %= p考虑这样一组数据 a = 10, b = 110, p = 100其实这个地方也可以加一句 a %= p,可以减少部分运算,也不会影响正确性,反正都是乘法,模意义下人人平等注意点2:这里的特判是什么意思如果p为1,相当于同余式恒成立,所以直接返回最小答案即可如果b为1,考虑 \\(\\forall a \e 0, a^0 = 1\\),所以直接返回最小答案即可注意点3:这就是上文中答案小于 \\(k\\) 情况的特判注意点4:由于是在模 \\(\\frac pD\\) 意义下,所以需要通过逆元的方式调整但是考虑到两者不知道是否互质,所以通过扩展欧几里得算法就行了扩展欧几里得算法可以参考:算法学习笔记(1): 欧几里得算法及其扩展注意点5:由于我们在求 \\(D\\) 的时候消耗了 \\(k\\) 个\\(a\\),所以需要加上 \\(k\\) 才是正确答案。归纳总结以上内容,生成内容时需要带简单的排版

正文

BSGS算法及其扩展算法

BSGS算法

所谓 Baby Step, Giant Step 算法,也就是暴力算法的优化

用于求出已知 \(a, b, p\), 且 \(p\) 为质数 时 \(a^x \equiv b \pmod p\) 的一个最小正整数解 \(x\)

下文中 \(a \perp p\) 指的是 \(a, p\) 互质

有两种情况:

  • \(a, p\) 不互质,又由于 \(p\) 是质数,意味着 \(a\)\(p\) 的倍数,所以 \(b\) 如果不为 \(0\) ,那么一定无解

  • 两者互质……即 \(a \perp p\) :

考虑到 \(a \perp p\) 意味着 \(a\)\(p\) 的完全剩余系中,也就是说 \(f(x) = a^x \pmod p\) 是一个周期为 |<a>| (……就是 \(a\) 在模 \(p\) 意义下的生成子群的大小) 的周期函数

实际上我们不会算出其周期的具体大小,我们只需要利用 |<a>| 恒 \(\le p\) 就行了

那么我们只需要枚举 \(x \in [0, p)\) 依次验证就可以求解了

但是暴力枚举没有那么优秀,对于出题人的数据可能过不了

所以我们就可以采用把毒瘤出题人吊起来打的方法BSGS算法进行优化


BSGS算法采用分块的思想 (分块?块大小就 \(\sqrt p\) 不就就行了吗,有什么好疑惑的?

假设块大小为 \(t\)

先参入 \(t\) 改写一下式子 \(a^x \equiv b \pmod p\)

得到 \(a^{it-j} \equiv b \pmod p\)

由于 \(a \perp p\) ,上述式子等价于 \(a^{it} \equiv (a^t)^i \equiv b a^j \pmod p\)

那么我们先预处理右式,枚举 \(j \in [0, t)\)\(ba^j \mod p\) 插入到一个Hash表中

也就是说 \(HashMap: \quad ba^j\ mod\ p \Rightarrow j\)

那么枚举 \(i \in [0, \lceil \frac pt \rceil]\) 计算出 \(a^{it} \mod p\) ,查找是否有对应的 \(j\),更新答案即可

时间复杂度为 \(O(t + \lceil \frac pt \rceil)\)

为了进一步优化时间复杂度,已知均值不等式 \(a + b \ge 2\sqrt{ab}\),当且仅当 \(a=b\) 时等号成立,所以我们令 \(t = \lceil \sqrt p \rceil\),那么时间复杂度就简化为了 \(O(\sqrt p)\)

常数为 \(2\) 可能还需要带一个 \(log\) ? QwQ

参考代码如下仅供参考

template<typename data>
data BSGS(data a, data b, data p) {
    b %= p;
    static std::map<data, data> hash; hash.clear();
    data t = std::ceil(std::sqrt(p)), v(1), j(0);
    for (; j < t; ++j) {
        // 此时 v = a^j % p
        hash[v * b % p] = j;
        v = v * a % p;
    }

    // 把 a 预处理成 a^t, 这样 (a^t)^i 就可以更快的算出了
    a = qpow(a, t, p), v = 1;
    // 如果此时 a 已经为 0 了,由于 t < p, p为质数所以 p|a (a为p的倍数)
    // 那么此时,如果 b 不为 0 则一定无解
    if (a == 0) return b == 0 ? 1 : -1;

    for (data s(0); s <= t; ++s) {
        // 此时 v = (a^t)^s
        j = hash.find(v) == hash.end() ? -1 : hash[v];
        if (~j && s * t >= j) return s * t - j;
        v = v * a % p;
    }
    return -1;
}

扩展BSGS算法 (exBSGS)

其实问题一摸一样,只是 \(p\) 不为质数了,也就是说其中 \(a, p\) 不一定互质

那么我们需要想办法使之变得互质,然后通过普通的 BSGS 算法求解

具体的,令 \(d_1 = gcd(a, p)\),如果 \(d_1 \nmid b\) 则原方程无解

这个我们可以将同余式转换:\(s a^x + tp = b\)

左式再转化为 \(d_1 (s a^{x-1} \frac a{d_1} + t \frac p {d_1}) = b\)

也就是说,如果 \(d_1 \nmid b\) ,那么一定无整数解

那么上述式子就可以转化为 \(sa^{x-1} \frac a{d_1} + t \frac p {d_1} = \frac b {d_1}\)

再换成同余式子,就变为了 \(\frac a {d_1}\cdot a^{x-1} \equiv \frac b {d_1} \pmod {\frac p {d_1}}\)

如果此时 \(a\)\(\frac p {d_1}\) 任然不互质,那么继续上述转化,令\(d_2 = gcd(a, \frac p {d_1})\)

\(\frac a {d_1 d_2} \cdot a^{x-2} \equiv \frac b {d_1 d_2} \pmod {\frac p {d_1 d_2}}\)

同理不断处理,直到 \(a \perp \frac p {d_1 d_2 \dots d_k}\)

我们记 \(D = \prod_{i = 1}^k d_i\)

那么最终的方程就是 \(\frac {a^k} {D} \cdot a^{x-k} \equiv \frac bD \pmod {\frac pD}\)

这样,我们把 \(\frac {a^k}D\) 通过逆元丢到方程右边,就成为了一个普通的 BSGS 问题了

注意一个细节,不排除解小于等于 \(k\) 的情况,所以在消去 \(d_i\) 的时候还要判断一下 \(a^i \equiv b \pmod p\) 是否可行

还有一些细节问题我会放在代码之后解释

参考代码仅供参考

// inv(i) i \equiv 1 mod p
// i * inv(i) + kp = 1 (kown i, p, 1)
template<typename T>
T inv(T i, T p) {
    T iv, k;
    exgcd(i, p, &iv, &k);
    iv %= p;
    // 注意点4
    return iv < 0 ? iv + p : iv;
}

data exBSGS(data a, data b, data p) {
    // 注意点 1 
    b %= p;
    // 注意点 2
    if (b == 1 || p == 1) return 0;
    data d, ak(1), k(0);
    while ((d = gcd(a, p)) != 1) {
        if (b % d) return -1;
        ++k, p /= d, b /= d;
        ak = a / d * ak % p;
        // 注意点 3
        if (ak == b) return k;
    }

    // 注意点 4
    b = b * inv(ak, p) % p;
    data res = BSGS(a, b, p);
    // 注意点 5
    if (~res) return res + k;
    return -1;
}

注意点1:为什么需要一次 b %= p

考虑这样一组数据 a = 10, b = 110, p = 100

其实这个地方也可以加一句 a %= p,可以减少部分运算,也不会影响正确性,反正都是乘法,模意义下人人平等

注意点2:这里的特判是什么意思

如果p为1,相当于同余式恒成立,所以直接返回最小答案即可

如果b为1,考虑到 \(\forall a \ne 0, a^0 = 1\),所以直接返回最小答案即可

注意点3:这就是上文中答案小于 \(k\) 情况的特判

注意点4

由于是在模 \(\frac pD\) 意义下,所以需要通过逆元的方式调整

但是考虑到两者不知道是否互质,所以通过扩展欧几里得算法就行了

扩展欧几里得算法可以参考:算法学习笔记(1): 欧几里得算法及其扩展

注意点5

由于我们在求 \(D\) 的时候消耗了 \(k\)\(a\),所以需要加上 \(k\) 才是正确答案

与算法学习笔记(10): BSGS算法及其扩展算法相似的内容:

算法学习笔记(10): BSGS算法及其扩展算法

BSGS算法及其扩展算法 BSGS算法 所谓 Baby Step, Giant Step 算法,也就是暴力算法的优化 用于求出已知 $a, b, p$, 且 $p$ 为质数 时 $a^x \equiv b \pmod p$ 的一个最小正整数解 $x$ 下文中 $a \perp p$ 指的是 $a,

算法学习笔记(29):分块

分块 这是一种基于根号的算法,核心为大块标记,散块暴力,做到复杂度的平衡。 可能第一个想到于此相关的就是莫队吧,这是利用分块优化暴力的方法。 目录分块Rmq Problem / mex[国家集训队] 排队 - 洛谷[TJOI2009] 开关 - 洛谷[Violet] 蒲公英 - 洛谷小小总结 Rmq

算法学习笔记(6): 树链剖分

树链剖分 树链剖分是一个很神奇,但是在树上可以完成一些区间操作问题 简单来说,就是把一棵树分成一条条的链,通过维护链上的信息来维护整棵树的信息 基础知识可以参考我的另外一篇博客:算法学习笔记(5): 最近公共祖先(LCA) 这里假设你已经掌握了上述博客中的所有相关知识,并清晰了其背后的原理 性质?发

算法学习笔记(11): 原根

原根 此文相对困难,请读者酌情食用 在定义原根之前,我们先定义其他的一点东西 阶 通俗一点来说,对于 $a$ 在模 $p$ 意义下的阶就是 $a^x \equiv 1 \pmod p$ 的最小正整数解 $x$ 或者说,$a$ 在模 $p$ 意义下生成子群的阶(群的大小) 再或者说,是 $a$ 在模

算法学习笔记(30):Kruskal 重构树

Kruskal 重构树 这是一种用于处理与最大/最小边权相关的一个数据结构。 其与 kruskal 做最小生成树的过程是类似的,我们考虑其过程: 按边权排序,利用并查集维护连通性,进行合并。 如果我们在合并时,新建一个节点,其权值为当前处理的边的权值,并将合并的两个节点都连向新建的节点,那么就可以得

算法学习笔记(3.1): ST算法

ST表 在RMQ(区间最值)问题中,著名的ST算法就是倍增的产物。ST算法可以在 \(O(n \log n)\) 的时间复杂度能预处理后,以 \(O(1)\) 的复杂度在线回答区间 [l, r] 内的最值。 当然,ST表不支持动态修改,如果需要动态修改,线段树是一种良好的解决方案,是 \(O(n)\

C++算法之旅、09 力扣篇 | 常见面试笔试题(上)算法小白专用

算法学习笔记,记录容易忘记的知识点和难题。详解时空复杂度、50道常见面试笔试题,包括数组、单链表、栈、队列、字符串、哈希表、二叉树、递归、迭代、分治类型题目,均带思路与C++题解

C++算法之旅、08 基础篇 | 质数、约数

算法学习笔记,记录容易忘记的知识点和难题。试除法、分解质因数、筛质数、约数个数、约数之和、最大公约数

算法学习笔记(1): 欧几里得算法及其扩展

扩展欧几里得算法详解 在了解扩欧之前我们应该先了解欧几里得算法 欧几里得算法 这是一个递归求最大公约数(greatest common divisor)的方法 $$ gcd(a, b) = gcd(b, a % b) $$ 可以通过一个类似的简单公式推导而来 好像叫做辗转相减法来着? $$ gcd(

算法学习笔记(3): 倍增与ST算法

倍增 目录倍增查找 洛谷P2249重点变式练习快速幂ST表扩展 - 运算扩展 - 区间变式答案倍增更多的用法优化矩形查询优化建图优化 DP作者有话说 倍增,字面意思即”成倍增长“ 他与二分十分类似,都是基于”2“的划分思想 那么具体是怎么样,我们以一个例子来看 查找 洛谷P2249 依据题面,我们知