Baby Step Giant Step 初步
引入
对于
a
,
b
,
m
∈
N
,
gcd
(
a
,
m
)
=
1
a,b,m\in\N,\gcd(a,m)=1
a,b,m∈N,gcd(a,m)=1 求解指数同余方程
a
x
≡
b
(
m
o
d
m
)
a^x\equiv b\pmod m
ax≡b(modm)
的最小正数解。
BSGS (Baby step Giant step)
我们考虑余数是存在循环节的,也就是说某个数
m
o
d
m
\bmod m
modm 只有
[
0
,
m
−
1
]
[0,m-1]
[0,m−1] 个取值,于是我们尝试寻找
a
x
m
o
d
m
a^x\bmod m
axmodm 的循环节。
由欧拉定理可知
a
φ
(
m
)
≡
1
(
m
o
d
m
)
a^{\varphi(m)}\equiv 1\pmod m
aφ(m)≡1(modm) ,因此有
a
x
m
o
d
m
a^x\bmod m
axmodm 的取值只有
φ
(
m
)
\varphi(m)
φ(m) 种,一旦
x
x
x 大于
φ
(
m
)
\varphi(m)
φ(m),那么就会与之前的值重复,答案不是最优。这个想法也可以从欧拉定理的推论
a
b
m
o
d
φ
(
m
)
≡
a
b
(
m
o
d
m
)
a^{b\bmod \varphi(m)}\equiv a^b\pmod m
abmodφ(m)≡ab(modm) 得到,万变不离其宗。
于是有暴力思想,枚举
[
1
,
φ
(
m
)
]
[1,\varphi(m)]
[1,φ(m)] 的数作为
x
x
x 的值,暴力判断是否满足方程。但是当
m
≥
1
0
8
m\ge 10^8
m≥108 且为质数时,
φ
(
m
)
=
m
−
1
≥
1
0
8
\varphi(m)=m-1\ge 10^8
φ(m)=m−1≥108 ,该思路任然会超时。
于是我们就引入了“优化暴力”的 B a b y s t e p G i a n t s t e p Baby~step~Giant~step Baby step Giant step 算法,简称 B S G S \mathbf{BSGS} BSGS
考虑
gcd
(
a
,
m
)
=
1
\gcd(a,m)=1
gcd(a,m)=1,因此我们可以对同余式进行整除运算(裴蜀定理可得吧,这里就不赘述了),即可以将
x
x
x 表示为
x
=
i
⋅
t
−
j
x=i\cdot t-j
x=i⋅t−j,我们设
t
=
⌈
m
⌉
t=\left\lceil\sqrt m\right\rceil
t=⌈m⌉。
于是原方程变为
a
i
⋅
t
−
j
≡
b
(
m
o
d
m
)
a
i
⋅
t
a
j
≡
b
(
m
o
d
m
)
a
i
⋅
t
≡
b
⋅
a
j
(
m
o
d
m
)
(
a
t
)
i
≡
b
⋅
a
j
(
m
o
d
m
)
a^{i\cdot t-j}\equiv b\pmod m\\ ~\\ \dfrac{a^{i\cdot t}}{a^j}\equiv b\pmod m\\ ~\\ a^{i\cdot t}\equiv b\cdot a^j\pmod m\\ ~\\ (a^t)^i\equiv b\cdot a^j\pmod m
ai⋅t−j≡b(modm) ajai⋅t≡b(modm) ai⋅t≡b⋅aj(modm) (at)i≡b⋅aj(modm)
此时我们将原方程的
x
x
x 分块来枚举,就可以均摊复杂度。
我们先枚举方程右边的
j
j
j ,然后将
j
j
j 取值,按照右边的余数对应存进一个
h
a
s
h
hash
hash 表里(说是这么说,但是我还是常用
m
a
p
map
map 或
u
n
o
r
d
e
r
e
d
unordered
unordered_
m
a
p
map
map ),然后同理枚举方程左边的
i
i
i ,查找是否有
j
j
j 与
i
i
i 方程两边余数匹配,即同余(这时候就用上先前的
h
a
s
h
hash
hash 表来查找),若匹配则直接得到答案
x
=
i
⋅
t
−
j
x=i\cdot t-j
x=i⋅t−j。
当
i
,
j
i,j
i,j 取
[
1
,
t
]
[1,t]
[1,t] 的值时,我们可以得到所有
x
∈
[
1
,
m
−
1
]
x\in[1,m-1]
x∈[1,m−1] 的解,显然符合先前所得的答案范围
[
1
,
φ
(
m
)
]
[1,\varphi(m)]
[1,φ(m)] 。
但是这个算法是可以通过的,时间为
Θ
(
m
)
\Theta(\sqrt m)
Θ(m) 。
此算法与原先纯暴力最大的不同,就是我们将枚举进行了分块,将枚举范围分为了两部分,各为
m
\sqrt m
m,这种思想有点像折半搜索(砍两瓣,分开搜索),将暴力复杂度拆开了。
这就是 B S G S \mathbf{BSGS} BSGS ,也就是一种优化了的暴力算法。
回忆一下思路,其实也很清晰,就是把答案分成两部分分别枚举然后对应查找,于是代码也很好实现。
int BSGS(int a,int b,long long p)
{
map<int,int> vis;
int t=sqrt(p)+1,mi=1;
for(int i=1;i<=t;i++)
{
if(i!=0) mi=(__int128)(mi*a)%p;
vis[(__int128)(b*mi)%p]=i;
}
a=mi;
if(!a) return (b%p==0)?1:-1; //特判,若a能被模数整除,那么当且仅当b也能被模数整除的情况下存在最小正数解 1,否则无解
mi=1;
for(int i=1;i<=t;i++)
{
map<int,int>::iterator it;
if(i!=0) mi=(__int128)(mi*a)%p; //暴力求余
it=vis.find(mi); //查找对应
if(it!=vis.end())
if((__int128)i*t-(it->second)>=0) return (__int128)(i*t-(it->second));
}
return -1;
}
总结
回过头来, B S G S \mathbf{BSGS} BSGS 真的不难理解,其实只要思路正确,暴力都能成为好算法。
奉上例题:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!