【学习笔记】Baby Step Giant Step算法及其扩展
1. 引入
Baby Step Giant Step算法(简称BSGS),用于求解形如\(a^x\equiv b\pmod p\)(\(a,b,p\in \mathbb{N}\))的同余方程,即著名的离散对数问题。
本文分为 \((a,p)=1\) 和 \((a,p)\neq 1\) 两种情况讨论。
2. 方程 \(a^x\equiv b \pmod p\) 的解性
因为若 \(a^{x}\equiv a^{x+n}\pmod p\),则 \(a^{x+i}\equiv a^{x+n+i}\)。由抽屉原理以及同余的性质得,\(a^x\) 对 \(p\) 的模具有周期性,最大周期不超过 \(p\)。由这个周期性可得,方程有解,等价于在 \([0,p)\) 中有解。由此我们只需要试图找到 \([0,p)\) 中的最小自然数解,就可以得到方程是否有解,以及根据指数模的周期性得到方程的通解。
下面我们只讨论求解方程的最小自然数解。
3. 求解 \(a^x\equiv b\pmod p\) \((a,p)=1\)
Baby Step Giant Step它的名字就告诉我们,这个算法要将问题规模由大化小。我们令 \(t=\lceil \sqrt{p}\rceil\),那么我们所有 \([0,p)\) 的自然数都可以包含在集合 \(\{x|x=it-j,\ i\in [1,t],\ j \in (0,t]\}\) 中。我们只需要验证这个集合中是否存在解,即可验证原方程是否存在解。
我们考虑将方程表示为
\[
a^{it-j}\equiv b\pmod p
\]
根据 \((a,p)=1\),我们有
\[a^{it}\equiv ba^j\pmod p\]
我们发现等号两边的数我们都可以在 \(O(\sqrt p)\) 的时间内枚举出来。
我们只需要 \(O(\sqrt p)\) 枚举出所有 \(ba^j\bmod p(\ j \in (0,t])\),然后把它们插入到哈希表当中,哈希表的对应位置储存这个值对应的最大的 \(j\)(因为要求最小自然数解,所以在 \(i\) 相同的时候要使 \(j\) 最大)。
然后 \(O(\sqrt p)\) 从小到大枚举我们需要的 \(a^{it}\bmod p(\ i\in [1,t])\),并在哈希表中查询是否有相等的值,若存在则取最小的 \(i\) 及对应最大的 \(j\),并将 \(it-j\) 作为最小自然数解;否则若不存在则说明无自然数解。
inline int solve_BSGS(const int &a, const int &b, const int &p)
{
int t = ceil(sqrt(p));
std::map<int, int> hash;
//map实现hash表
hash.clear();
int tmp = b;
for (int i = 1; i <= t; ++i)
{
tmp = 1LL * tmp * a % p;
hash[tmp] = i;
}
//插入b*a^j
int pw = qpow(a, t, p);
tmp = pw;
for (int i = 1; i <= t; ++i)
{
if (hash.find(tmp) != hash.end())
return i * t hash[tmp];
tmp = 1LL * tmp * pw % p;
}
//查询a^(it)
return -1; //返回无解
}
4. 求解 \(a^x\equiv b\pmod p\) \((a,p)\neq 1\)
对于这个方程,我们不能像上面那样求解的原因就是 \(a\) 在模 \(p\) 意义下不存在逆元,不能将 \(a^{it-j}\) 表示为 \(a^{it}\times a^{-j}\)。那么我们从不定方程的角度分析这个同余方程。
这个方程等价于
\[a^x+py=b\]
令 \(a_1=(a,p)\),令原方程化为
\[a^{x-1}\frac{a}{a_1}+\frac{p}{a_1}y=\frac{b}{a_1}\]
若此时 \((a,\frac{p}{a_1})\neq 1\),那么我们接着化成
\[a^{x-2}\frac{a^2}{a_1a_2}+\frac{p}{a_1a_2}y=\frac{b}{a_1a_2}\]
同理不断进行这样的操作,最后我们达到 \((a,\frac{p}{a_1a_2\dots a_n})=1\)的目标,并将方程化为
\[a^{x-n}\frac{a^n}{a_1a_2\dots a_n}+\frac{p}{a_1a_2\dots a_n}y=\frac{b}{a_1a_2\dots a_n}\]
然后记 \(a'=\frac{a}{a_1a_2\dots a_n}\),\(p'=\frac{p}{a_1a_2\dots a_n}\),\(b'=\frac{b}{a_1a_2\dots a_n}\),那么原不定方程可以化为同余方程
\[a^{x-n}a'\equiv b'\pmod{p'}\]
显然 \((a',p')=1\),因此我们可以写成
\[a^{x-n}\equiv b'(a')^{-1}\pmod{p'}\]
然后就可以用互质的方法解决了。可以发现,每次在 \(p\) 中除去一个最大公约数,每次都会有至少同一个质因子的次数减少 \(1\),那么在int
范围内,\(n\) 最多只会取到 \(30\)。以上两个情况的时间复杂度均为 \(O(\sqrt{p}\log_2{\sqrt p})\),因为用map实现哈希,可以做到更优秀。
有一些值得注意的地方:
1. 在我们令 \(b\) 除以一个最大公约数 \(d\) 时,若 \(d\nmid b\),结合求解二元不定方程的知识,我们判定方程无自然数解。
2. 我们在不互质情况的化简操作中,已经假定了 \(x\ge n\),所以方程才能写成那样的互质形式。对于 \(x\le n\) 的情况,我们应当提前枚举判断。
3. 注意在求解方程之前将 \(a,b\) 对 \(p\) 取模,注意 \(a\) 取模后为 \(0\) 的情况。
4. 对于对时间限制要求较为紧的题目,应当使用更为优秀的哈希表实现方式。
模板题:SP3105 MOD Power Modulo Inverted
洛谷链接:https://www.luogu.org/problemnew/show/SP3105
代码:
//map水过
#include <map>
#include <cmath>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
int a, p, b;
inline int qpow(int b, int p, const int &mod)
{
int res = 1;
for (; p; p >>= 1, b = 1LL * b * b % mod)
if (p & 1)
res = 1LL * res * b % mod;
return res;
}
inline int ex_gcd(const int &a, const int &b, int &x, int &y)
{
if (!b)
return x = 1, y = 0, a;
int res = ex_gcd(b, a % b, y, x);
return y -= a / b * x, res;
}
inline int solve_equ(const int &a, const int &b, const int &c)
{
int x, y;
int d = ex_gcd(a, b, x, y);
if (c % d != 0) return -1;
int mod = b / d;
return (1LL * c / d * x % mod + mod) % mod;
}
inline int solve_BSGS(const int &a, const int &b, const int &p)
{
int t = ceil(sqrt(p));
std::map<int, int> hash;
hash.clear();
int tmp = b;
for (int i = 1; i <= t; ++i)
{
tmp = 1LL * tmp * a % p;
hash[tmp] = i;
}
int pw = qpow(a, t, p);
tmp = pw;
for (int i = 1; i <= t; ++i)
{
if (hash.find(tmp) != hash.end())
return i * t - hash[tmp];
tmp = 1LL * tmp * pw % p;
}
return -1;
}
inline bool check()
{
int k = 1 % p;
for (int i = 0; i <= 40; ++i)
{
if (k == b)
return printf("%d\n", i), true;
k = 1LL * k * a % p;
}
if (!a)
return puts("No Solution"), true;
return false;
}
int main()
{
while (scanf("%d%d%d", &a, &p, &b), a || p || b)
{
a %= p, b %= p;
if (check())
continue;
int d;
int ap = 1, n = 0;
bool flg = false;
while ((d = std::__gcd(a, p)) != 1)
{
++n;
ap = 1LL * ap * (a / d) % p;
p /= d;
if (b % d)
{
flg = true;
break;
}
b /= d;
}
if (flg)
puts("No Solution");
else
{
int res = solve_BSGS(a, 1LL * b * solve_equ(ap, p, 1) % p, p);
if (res == -1)
puts("No Solution");
else
printf("%d\n", res + n);
}
}
return 0;
}
【学习笔记】Baby Step Giant Step算法及其扩展的更多相关文章
- POJ 3243 Clever Y (求解高次同余方程A^x=B(mod C) Baby Step Giant Step算法)
不理解Baby Step Giant Step算法,请戳: http://www.cnblogs.com/chenxiwenruo/p/3554885.html #include <iostre ...
- 解高次同余方程 (A^x=B(mod C),0<=x<C)Baby Step Giant Step算法
先给出我所参考的两个链接: http://hi.baidu.com/aekdycoin/item/236937318413c680c2cf29d4 (AC神,数论帝 扩展Baby Step Gian ...
- HDU 2815 Mod Tree 离散对数 扩张Baby Step Giant Step算法
联系:http://acm.hdu.edu.cn/showproblem.php?pid=2815 意甲冠军: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQ ...
- 『高次同余方程 Baby Step Giant Step算法』
高次同余方程 一般来说,高次同余方程分\(a^x \equiv b(mod\ p)\)和\(x^a \equiv b(mod\ p)\)两种,其中后者的难度较大,本片博客仅将介绍第一类方程的解决方法. ...
- HDU 2815 扩展baby step giant step 算法
题目大意就是求 a^x = b(mod c) 中的x 用一般的baby step giant step 算法会超时 这里参考的是http://hi.baidu.com/aekdycoin/item/2 ...
- 数论之高次同余方程(Baby Step Giant Step + 拓展BSGS)
什么叫高次同余方程?说白了就是解决这样一个问题: A^x=B(mod C),求最小的x值. baby step giant step算法 题目条件:C是素数(事实上,A与C互质就可以.为什么?在BSG ...
- 【POJ2417】baby step giant step
最近在学习数论,然而发现之前学的baby step giant step又忘了,于是去翻了翻以前的代码,又复习了一下. 觉得总是忘记是因为没有彻底理解啊. 注意baby step giant step ...
- [置顶] hdu2815 扩展Baby step,Giant step入门
题意:求满足a^x=b(mod n)的最小的整数x. 分析:很多地方写到n是素数的时候可以用Baby step,Giant step, 其实研究过Baby step,Giant step算法以后,你会 ...
- POJ 2417 Discrete Logging ( Baby step giant step )
Discrete Logging Time Limit: 5000MS Memory Limit: 65536K Total Submissions: 3696 Accepted: 1727 ...
随机推荐
- 【LeetCode】整数反转【不能借助辅助空间,需要处理溢出】
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例 1: 输入: 123 输出: 321 示例 2: 输入: -123 输出: -321 示例 3: 输入: 120 输出: ...
- TCP粘包和拆包的定义,产生的原因以及解决方案
TCP粘包:指发送方发送的若干数据包在接收方接收时粘成一团,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾 产生的原因: 1.发送方的原因:TCP默认使用Nagle算法,而Nagle算法主要做两件 ...
- nginx acces.log日志分析
1,统计各访问IP的总数 awk '{if($9>0 && $9==200 && substr($6,2)== "GET") a[$1]++} ...
- SpringBoot项目使用RedisTemplate设置序列化方式
前端时间新项目使用SpringBoot的RedisTemplate遇到一个问题,先简单描述一下问题:不同项目之间redis共用一个,但是我们新项目读不到老项目存储的缓存.新项目搭建的时候没有跟老项目使 ...
- 题解 Luogu P3959 【宝藏】
来一篇不那么慢的状压??? 话说这题根本没有紫题难度吧,数据还那么水 我是不会告诉你我被hack了 一看数据规模,n≤12,果断状压. 然后起点要枚举,就设dp状态: f[i][j]=以i为起点到j状 ...
- 数列分段 II
题目描述 思路 代码 #include <cstdio> int n, m, arr[100005], ans; int l, r, mid, inf = 0x7f3f3f3f; inli ...
- tensorflow-简单的神经网络
本次笔记是关于tensorflow1的代码,由于接触不久没有跟上2.0版本,这个代码是通过简单的神经网络做一个非线性回归任务,(如果用GPU版本的话第一次出错就重启) import tensorflo ...
- LOJ2267 SDOI2017 龙与地下城 FFT、概率密度函数、Simpson
传送门 概率论神仙题-- 首先一个暴力做法是设\(f_{i,j}\)表示前\(i\)个骰子摇出点数和为\(j\)的概率,不难发现DP的过程是一个多项式快速幂,FFT优化可以做到\(O(XYlog(XY ...
- JDBC第一个案例
1.概述 JDBC(Java DataBase Connectivity) 是 Java 提供的用于执行 SQL 语句一套 API,可以为多种关系型数据库提供统一访问,由一套用 Java 语言编写的类 ...
- 导航条按钮的设置UIBarButtonItem
1.目的 2.代码 // 设置导航栏的按钮 UIButton *leftNavBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 35, 35 ...