本文原载于:http://www.orchidany.cf/2019/02/06/BSGS-junior/#more

\(\rm{0x01}\) \(\mathcal{Preface}\)

\(\rm{BSGS}(Baby~Step~Giant~Step)\), 大步小步法。当然也会被叫做拔山盖世北上广深算法……咳,这并不重要。形式化地讲, \(\rm{BSGS}\)算法主要用来解决以下问题 :

给定质数\(p\), 整数\(a, b\), \((a, p)=1\).求最小的非负整数\(x\), 使得\(a^x≡ b~(\mod p)\)

而首先我们知道的,是由欧拉定理\(a ^{\varphi(p)} ≡ 1 ~(\mod p)​\),并且我们还知道\(a^0=1≡1 ~(\mod p)​\),所以我们可以得出一个断言:

如果方程\(a^x≡ b~(\mod p)​\)有最小非负整数解,那么最小非负整数解一定在\([0, \varphi(p))​\)中 \(\qquad \qquad(1) ​\)

此处肉眼可以看出其循环节为\(\varphi(p)\),不再证明。

之后我们将以此为基础进行类似分块的操作——

\(\rm{0x02~~Baby~Step~Giant~Step}\)

首先我们记\(n=\sqrt {\varphi(p)}\),那么\(\forall x \in [0, \varphi(p))\), \(x = i\times m+j\), \(i \leq \lfloor \frac{p−1-m}{m} \rfloor,~~ 0≤j <m\) 。那么对于原方程我们可以把其改为:$$a^{i\cdot n+j}≡ b~(\mod p)$$移一下项就可以变成$$a^j ≡b \cdot a^{-i\cdot n} (\mod p)$$那么现在我们的策略是算出所有\(a^j\)来,在\(\mod p\) 意义下观察是否有一个\(i\)使得\(a^j ≡b \cdot a^{-i\cdot n} (\mod p)\)。我们称左边枚举\(a^j\)叫做小步\((\rm{Baby~Step})\), 称右边枚举\(b \cdot a^{-i\cdot n}\)叫做大步\(~(\rm{Giant~Step})\)

那么其实算法流程很明晰了,我们只需要循环两次、第一次记录的\(a^j\)用哈希表(\(STL\)自带\(unordered\)_ \(map\))记录一下即可。

inline LL expow(LL a, LL b, LL p){
LL res = 1 ;
while (b){
if (b & 1)
(res *= a) %= p ;
(a *= a) %= p, b >>= 1 ;
}
return res % p ;
}
inline void bsgs(LL x, LL y, LL p){
P = ceil(sqrt(p)), Hash.clear(), Q = expow(x, -P + 2 *(p - 1), p) ;
//a ^ (p-1) = 1 (mod p) => Q = a^(-P) = a ^(-P + p -1) ;
for (LL i = 1, j = 0 ; j < P ; ++ j, (i *= x) %= p)
if (!Hash.count(i)) Hash[i] = j ; // Push them into hash_table
for (LL i = y, j = 0 ; j <= P ; ++ j, (i *= Q) %= p)
if (Hash.count(i)){ cout << Hash[i] + j * P << endl ; return ; }
cout << "-1" << endl ;
}

其中细节还是有的:

  • 计算sqrt时要上取整

  • 我们在求\(a^{-i\cdot n}​\)时用的底变量需要由费马小定理求快速幂得出。但是此时指数上可能为负数,所以我们选择加上一个模数,不影响结果。

  • 两次循环枚举的边界要注意有的是\(\leq\)有的是\(<\)

  • 算法还没开始时,要判断本身\(a\)是否可以被\(P\)整除。如果不特判这种情况的话,我们上面代码中的Q就会=0,从而在下面的第二个循环处出错——我们的hash[i]j不能同时为\(0\),从而输出错误的答案。

\(\rm{0x03}\) 例题

\(T1~\)\(LuoguP4028\)

裸题,但是有很多坑……或者说上面列举的细节都涵盖了qaq

#include <cmath>
#include <cstdio>
#include <iostream>
#include<tr1/unordered_map> #define LL long long using namespace std ;
using namespace tr1 ; int T ;
LL A, B, M, P, Q ; unordered_map <LL, LL> Hash ; inline LL expow(LL a, LL b, LL p){
LL res = 1 ;
while (b){
if (b & 1)
(res *= a) %= p ;
(a *= a) %= p, b >>= 1 ;
}
return res % p ;
}
inline void bsgs(LL x, LL y, LL p){
P = ceil(sqrt(p)), Hash.clear(), Q = expow(x, -P + 2 *(p - 1), p) ;
//a ^ (p-1) = 1 (mod p) => Q = a^(-P) = a ^(-P + p -1) ;
for (LL i = 1, j = 0 ; j < P ; ++ j, (i *= x) %= p)
if (!Hash.count(i)) Hash[i] = j ; // Push them into hash_table
for (LL i = y, j = 0 ; j <= P ; ++ j, (i *= Q) %= p)
if (Hash.count(i)){ cout << Hash[i] + j * P << endl ; return ; }
cout << "Couldn't Produce!" << endl ;
}
inline LL qr(){
LL res = 0 ; char c = getchar() ; while (!isdigit(c)) c = getchar() ;
while (isdigit(c)) res = (res << 1) + (res << 3) + c - 48, c = getchar() ;
return res ;
}
int main(){
cin >> T ;
while (T --){
M = qr(), A = qr(), B = qr() ;
if ((!(A % M == 0 && B))) bsgs(A, B, M) ;
else cout << "Couldn't Produce!" << endl ;
}
return 0 ;
}

\(T2~\) \(TJOI2007~Cute~Prime​\)

最裸最裸的、无特判的题……可以水一下双倍经验。

\(\mathfrak{writter: pks}\)

BSGS-Junior·大步小步算法的更多相关文章

  1. [模板]大步小步算法——BSGS算法

    大步小步算法用于解决:已知A, B, C,求X使得 A^x = B (mod C) 成立. 我们令x = im - j | m = ceil(sqrt(C)), i = [1, m], j = [0, ...

  2. 离散对数及其拓展 大步小步算法 BSGS

    离散对数及其拓展 离散对数是在群Zp∗Z_{p}^{*}Zp∗​而言的,其中ppp是素数.即在在群Zp∗Z_{p}^{*}Zp∗​内,aaa是生成元,求关于xxx的方程ax=ba^x=bax=b的解, ...

  3. 离散对数&&大步小步算法及扩展

    bsgs algorithm ax≡b(mod n) 大步小步算法,这个算法有一定的局限性,只有当gcd(a,m)=1时才可以用 原理 此处讨论n为素数的时候. ax≡b(mod n)(n为素数) 由 ...

  4. 【题解】Matrix BZOJ 4128 矩阵求逆 离散对数 大步小步算法

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4128 大水题一道 使用大步小步算法,把数字的运算换成矩阵的运算就好了 矩阵求逆?这么基础的线 ...

  5. 大步小步算法模板题, poj2417

    大步小步模板 (hash稍微有一点麻烦, poj不支持C++11略坑) #include <iostream> #include <vector> #include <c ...

  6. [BSGS]大步小步算法

    问题 BSGS被用于求解离散对数,即同余方程: \[ A^x\equiv B\pmod{P} \] 求\(x\)的最小非负整数解. 保证\(A\perp P\)(互质). 分析 首先,我们根据费马小定 ...

  7. BSGS算法(大步小步算法)

    计算\(y^x ≡ z \ mod\ p\) 中 \(x\) 的解. 这个模板是最小化了\(x\) , 无解输出\(No \ Solution!\) map<ll,ll>data; ll ...

  8. 洛谷 - P4861 - 按钮 - 扩展大步小步算法

    https://www.luogu.org/problemnew/show/P4861 把好像把一开始b==1的特判去掉就可以AC了. #include<bits/stdc++.h> us ...

  9. UVA 11916 Emoogle Grid 离散对数 大步小步算法

    LRJ白书上的题 #include <stdio.h> #include <iostream> #include <vector> #include <mat ...

随机推荐

  1. SSM实现简单后台分页

    1.简单思路 这是最常见的分页格式,分析一下我们需要传什么数据给前端吧! 首先是左边下面的总共几条记录,然后是右边的当前页面,然后就是一些你所需要展示的数据.对了每页显示多少条是不也得控制下,下面的显 ...

  2. C++桥接模式【转】

    https://www.cnblogs.com/jiese/p/3164940.html 将抽象部份与它的实现部份分离,使它们都可以独立地变化. 桥接模式号称设计模式中最难理解的模式之一,关键就是这个 ...

  3. mysql学习目录

    MySQL数据库 mysql 之库, 表的简易操作 mysql之行(记录)的详细操作 mysql之单表查询 mysql之多表查询 Navicat安装及简单使用 mysql之Navicat工具.pymy ...

  4. Chrome浏览器跨域

    配置新版Chrome浏览器跨域,需要创建用户数据文件夹,在其中保存浏览器的缓存.历史记录.收藏夹等数据. Windows系统Chrome跨域 1 下载Chrome 64位绿色版,解压缩,并在桌面创建快 ...

  5. Java字符串占位符(commons-text)替换(转载)

    Java字符串占位符(commons-text)替换 https://blog.csdn.net/varyall/article/details/83651798 <dependency> ...

  6. Linux 磁盘分区方案简析

    Linux 磁盘分区方案简析 by:授客 QQ:1033553122   磁盘分区 任何硬盘在使用前都要进行分区.硬盘的分区有两种类型:主分区和扩展分区.一个硬盘上最多只能有4个主分区,其中一个主分区 ...

  7. Android开发常用的一些功能列表(转)

    文章来源:http://www.cnblogs.com/netsql/archive/2013/03/02/2939828.html 1.软件自动更新下载,并提示 2.软件登录注册,以及状态保存 3. ...

  8. Units in Android

    一般使用dp,不使用px.sp啥时候用呢?给TextView设置文字大小的时候用.

  9. Android基础之6.0系统以上的权限分配

    public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle saved ...

  10. scrapy系列(二)——startproject、genspider创建项目与模板使用

    阅读本文之前需要安装scrapy,如果你还没有安装该框架,那么可以看之前一篇文章scrapy1.2windows安装. 现在默认大家都已经成功的安装了scrapy可以开始大展身手了.本文主要讲的是新建 ...