BSGS简介
BSGS算法,全称Baby Step Giant Step算法,用于求解关于 x x x的形如 a x ≡ b ( m o d p ) a^{x} \equiv b \pmod p ax≡b(modp) , p p p为质数的方程。
求解过程
不妨设
x
=
i
m
−
j
x=im-j
x=im−j,其中
m
=
⌈
p
⌉
m=\lceil\sqrt{p}\rceil
m=⌈p⌉,
j
∈
[
0
,
m
)
j \in [0,m)
j∈[0,m),
i
∈
[
1
,
m
]
i \in [1,m]
i∈[1,m]。于是原方程转化为:
a
i
m
−
j
≡
b
(
m
o
d
p
)
a^{im-j} \equiv b \pmod p
aim−j≡b(modp)
继续转化:
a
i
m
≡
a
j
b
(
m
o
d
p
)
a^{im} \equiv a^{j}b \pmod p
aim≡ajb(modp)
于是我们可以枚举
j
j
j,并将
a
j
b
m
o
d
  
p
a^{j}b \mod p
ajbmodp的答案记录在map中(当然如果你是手写哈希表的巨佬,那就当我没说)。map中的key存的是答案,value存的是
j
j
j。
然后我们再枚举 i i i,在map中查找 a i m m o d    p a^{im}\mod p aimmodp,如果查询到了,那么查询到的即为 j j j,那么 x = i m − j x=im-j x=im−j即为答案。
一些证明
注意到我们求解时取了 m = ⌈ p ⌉ m=\lceil\sqrt{p}\rceil m=⌈p⌉,这能保证我们枚举的时间复杂度均为 O ( p ) O(\sqrt p) O(p),但是却限定了答案 x ∈ [ 0 , p ] x \in [0,p] x∈[0,p],那么如何保证解一定在这个范围内呢?
证明过程需要费马小定理: a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod p ap−1≡1(modp),其中 p p p为质数, g c d ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1。
引理
a k m o d    ( p − 1 ) ≡ a k ( m o d p ) a^{k\mod(p-1)}\equiv a^{k} \pmod p akmod(p−1)≡ak(modp)
条件与费马小定理相同。
证明:我们可以把
k
m
o
d
  
(
p
−
1
)
k\mod (p-1)
kmod(p−1)看做
k
−
n
(
p
−
1
)
k-n(p-1)
k−n(p−1),那么原方程化为:
a
k
(
a
p
−
1
)
n
≡
a
k
(
m
o
d
p
)
\frac{a^{k}}{(a^{p-1})^{n}} \equiv a^k \pmod p
(ap−1)nak≡ak(modp)
通过费马小定理,可知
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1} \equiv 1 \pmod p
ap−1≡1(modp),所以原式显然成立。
正式的证明
其实知道引理后直接一个显然就好啦
由引理可知,即使在 ( p , + ∞ ) (p,+\infty) (p,+∞)中存在解,我们依旧可以通过 m o d    ( p − 1 ) \mod (p-1) mod(p−1)操作在$ [0,p]$内找到解。
代码
-1表示无解
ll bsgs(ll a, ll b, ll p){
a %= p, b %= p;
if(!a && !b) return 1;
if(!a || !b) return -1;
mp.clear(); //初始化map
ll m = ceil(sqrt(p*1.0)), tmp = 1;
mp[b] = 0;
for(int j = 1; j <= m; j++){ //枚举a^j*b
tmp = tmp*a % p;
if(!mp[tmp*b%p])
mp[tmp*b%p] = j;
}//循环完成后tmp=a^m
ll t = 1, ans;
for(int i = 1; i <= m; i++){ //枚举a^(im)
t = t*tmp%p;
if(mp[t]){ //判断是否存在
ans = i*m-mp[t];
return (ans%p+p)%p;
}
}
return -1;
}
例题(水经验)
前两个操作是快速幂和扩欧的模板,第三个操作是BSGS模板。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n, t;
ll qpow(ll a, ll n, ll mod){
ll res = 1, base = a;
while(n){
if(n&1) res = (res*base)%mod;
base = (base*base)%mod;
n >>= 1;
}
return res;
}
ll exgcd(ll a, ll b, ll &x, ll &y){
if(!b) {
x = 1, y = 0;
return a;
}
ll res = exgcd(b, a%b, x, y);
ll t = x;
x = y;
y = t-a/b*y;
return res;
}
map<ll, ll> mp;
ll bsgs(ll a, ll b, ll p){
a %= p, b %= p;
if(!a && !b) return 1;
if(!a || !b) return -1;
mp.clear();
ll m = ceil(sqrt(p*1.0)), tmp = 1;
mp[b] = 0;
for(int j = 1; j <= m; j++){
tmp = tmp*a % p;
if(!mp[tmp*b%p])
mp[tmp*b%p] = j;
}
ll t = 1, ans;
for(int i = 1; i <= m; i++){
t = t*tmp%p;
if(mp[t]){
ans = i*m-mp[t];
return (ans%p+p)%p;
}
}
return -1;
}
int main()
{
cin >> n >> t;
ll y, p, z;
for(int i = 1; i <= n; i++){
scanf("%lld%lld%lld", &y, &z, &p);
if(t == 1){
printf("%lld\n", qpow(y, z, p));
}
else if(t == 2){
ll x, t;
ll g = exgcd(y, p, x, t);
if(z % g){
puts("Orz, I cannot find x!");
continue;
}
y /= g, z /= g, p /= g;
x = (x*z%p+p)%p;
printf("%lld\n", x);
}
else{
ll ans = bsgs(y, z, p);
if(ans == -1){
puts("Orz, I cannot find x!");
}
else{
printf("%lld\n", ans);
}
}
}
return 0;
}