Baby Steps Giant Steps(BSGS)及其扩展——杨子曰算法
超链接:数学合集
又名巴士公司,北上广深,拔山盖世……
感叹:中华汉字真是博大精深啊!
BSGS
他可以干嘛捏?
解方程:
a
x
≡
b
(
m
o
d
p
)
a^x\equiv b\ (mod\ p)
ax≡b (mod p)的最小非负整数解,不过a和p是互质滴
首先,我来告诉大家一个一个神奇的事实:
如果这个方程有解,那么最小解一定在[0,p-1)里
想要说明这个事情我们需要引出一个东西:幂取模的循环节
相信大家的小学的时候一定(不,我没做过)做过这样一个问题:
3
233
的
个
位
是
什
么
?
3^{233}的个位是什么?
3233的个位是什么?
然后老师会告诉你这样去处理:找循环节
3
1
⇒
个
位
是
3
3
2
⇒
个
位
是
9
3
3
⇒
个
位
是
7
3
4
⇒
个
位
是
1
3
5
⇒
个
位
是
3
3^1\Rightarrow 个位是3\\ 3^2\Rightarrow 个位是9\\ 3^3\Rightarrow 个位是7\\ 3^4\Rightarrow 个位是1\\ 3^5\Rightarrow 个位是3
31⇒个位是332⇒个位是933⇒个位是734⇒个位是135⇒个位是3
欧,有没有发现
3
5
3^5
35的个位就和
3
1
3^1
31的个位一样了,这就说明如果我再往下,那么就又是3971,3971,不停循环,每4个循环一次
然后我们用
233
m
o
d
4
=
1
233\ mod \ 4=1
233 mod 4=1,得到答案3,然后老师就会奖励你一颗小行星小星星
上面那个问题其实告诉了我们一个事情:
3
x
≡
b
(
m
o
d
10
)
3^x \equiv b(mod\ 10)
3x≡b(mod 10)
这个b的取值只有4个
那么我们能不能再把这个事情稍稍扩展一下,变成:
a
x
≡
b
(
m
o
d
p
)
a^x \equiv b(mod\ p)
ax≡b(mod p)
会产生循环节捏?——当然是可以的,原因很简单:由于我们要看的是模p以后的值,所以我们根本不关心其他的东西,而
a
x
m
o
d
p
=
a
x
−
1
m
o
d
p
∗
x
m
o
d
p
a^x \ mod\ p=a^{x-1}\ mod\ p*x\ mod\ p
ax mod p=ax−1 mod p∗x mod p
So,当两次模完p以后值一样时,就产生循环节了
现在我们再来看一下我们最终要解决的问题:解 a x ≡ b ( m o d p ) a^x\equiv b\ (mod\ p) ax≡b (mod p)的最小非负整数解,a和p互质
由于左边那个东西会产生循环节,而我们要得到的也只要是x的最小非负整数解,那我们是不是只要考虑循环节里能取到的值那不能等于b,然后输出对应的x就好了
那么接下来我们就来考虑一下这个循环节最长会有多长?
我们可以用两个办法来思考这个问题:
- 前面已经说过了,如果两个 a x m o d p a^x\ mod\ p ax mod p取到了相同的值,那么就可以产生循环节,然而一个数模p的取值最多能有多少种捏?只能取到——1~p-1(由于a和p互质,So,取不到0),根据鸽笼原理,p个数必定会出现两个数取值相同,So,最多p-1个数一定会能构成循环节,这就说明解一定在[0,p-1)里(这里确实是p-1个数)
- 首先: a 0 ≡ 1 ( m o d p ) a^0\equiv 1(\ mod\ p) a0≡1( mod p),由于a,p互质,再根据欧拉定理(那是啥?戳我)得到: a φ ( p ) ≡ 1 ( m o d p ) a^{\varphi(p)}\equiv 1(\ mod\ p) aφ(p)≡1( mod p),也就是说从0开始到 φ ( p ) \varphi(p) φ(p)就一定循环了,而 φ ( p ) \varphi(p) φ(p)最大只能取到p-1(当p是质数的时候),So,最多最多到p-1一定有循环节了,于是我们成功得到了如果这个方程有解,解一定在[0,p-1)里
讲到这里,一定有人会说:那就从0~p-1暴枚一遍,非常优秀!
我们不是野蛮人,我们要讲究效率,来把复杂度降到 O ( p ) O(\sqrt p) O(p),使用一个类似分块的想法(那是什么,戳)
令 t = ⌈ p ⌉ t=\left\lceil\sqrt p\right\rceil t=⌈p⌉
我们不妨设
x
=
i
∗
t
−
j
x=i*t-j
x=i∗t−j,那么i在[0,t]里,j在[0,t)里,这样一定可以覆盖所有情况,那么方程就是:
a
i
∗
t
−
j
≡
b
(
m
o
d
p
)
a^{i*t-j} \equiv b(mod\ p)
ai∗t−j≡b(mod p)
于是就变成了这样:
(
a
t
)
i
≡
b
∗
a
j
(
m
o
d
p
)
(a^t)^i \equiv b*a^j(mod\ p)
(at)i≡b∗aj(mod p)
然后你就会发现右边的取值只有
⌈
p
⌉
\left\lceil\sqrt p\right\rceil
⌈p⌉种,左边的取值也只有
⌈
p
⌉
\left\lceil\sqrt p\right\rceil
⌈p⌉种,那我们就可以美滋滋地匹配了
先枚举j,把右边的所有情况扔到一个hash里(我们管这个叫Baby Steps),然后枚举i,算出左边的值,在hash表里寻找有没有(我们管这个叫Giant Steps),完事!
stl大法好
ll bsgs(ll a,ll b,ll p){//return -1 表示无解
if (b>=p) return -1;
map<ll,ll> mp;
mp.clear();
b%=p;
ll t=(ll)sqrt(p)+1;
for (ll j=0;j<t;j++){
ll v=b*pow(a,j,p)%p;
mp[v]=j;
}
a=pow(a,t,p);
if (a==0){
if (b==0) return 1;
else return -1;
}
for (ll i=0;i<=t;i++){
ll v=pow(a,i,p);
if (mp.find(v)!=mp.end()){
ll tmp=mp[v];
if (i*t-tmp>=0) return i*t-tmp;
}
}
return -1;
}
扩展BSGS
也就是问题变成a和p不互质了,那怎么办办捏?
那就把a和p变成互质的不就完了吗?
我们让
d
=
g
c
d
(
a
,
p
)
d=gcd(a,p)
d=gcd(a,p)
我们把整个式子除掉d(如果在这个过程中d不整除b,那么除非b=1,否则就无解了),得到了:
a
x
d
≡
b
d
(
m
o
d
p
d
)
\frac{a^x}{d}\equiv \frac{b}{d}(mod\ \frac{p}{d})
dax≡db(mod dp)
把一个
a
x
−
1
a^{x-1}
ax−1拎出来:
a
x
−
1
∗
a
d
≡
b
d
(
m
o
d
p
d
)
a^{x-1}*\frac{a}{d}\equiv \frac{b}{d}(mod\ \frac{p}{d})
ax−1∗da≡db(mod dp)
现在想象一下,如果我们把这个 a d \frac{a}{d} da除到等号右边的话(程序中先不要在这里移项),方程是不是就变回原来的格式了!
原来的 a x a^x ax变成了 a x − 1 a^{x-1} ax−1,p变成了 p d \frac{p}{d} dp
肯定有人会说了: a x − 1 a^{x-1} ax−1和 p d \frac{p}{d} dp也不一定是互质的呀!
不要紧,再重复上面的操作直到d是1呗,那么最这个方程会长这样:
a
x
−
k
∗
a
k
∏
d
i
≡
b
∏
d
i
(
m
o
d
p
∏
d
i
)
a^{x-k}*\frac{a^k}{\prod d_i}\equiv \frac{b}{\prod d_i}(mod\ \frac{p}{\prod d_i})
ax−k∗∏diak≡∏dib(mod ∏dip)
现在我们把 a k ∏ d i \frac{a^k}{\prod d_i} ∏diak移过去(这里要使用逆元了,不会的童鞋戳我)
方程就是原来的格式了,而且这个时候 a x − k a^{x-k} ax−k和 p ∏ d i \frac{p}{\prod d_i} ∏dip一定互质了
然后我们就可以美滋滋地使用BSGS了(这时求出来的是x-k,答案别忘了加k)!
但是在这之前,我们还要考虑答案在0~k之间的情况,k是log级的,暴力都没问题,不过这个部分是可以在上面除gcd的时候顺便做掉的
OK,完事
ll ex_bsgs(ll a,ll b,ll p){
if (b==1) return 0;
ll d=gcd(a,p),k=0,s=1;
while(d>1){
if (b%d!=0) return -1;
k++;
b/=d;
p/=d;
s=(s*a/d)%p;
if (s==b) return k;
d=gcd(a,p);
}
ll x,y;
ex_gcd(s,p,x,y);
b=(b*x%p+p)%p;
ll tmp=bsgs(a,b,p);
if (tmp==-1) return -1;
else return tmp+k;
}
参考:
https://www.cnblogs.com/sdzwyq/p/9900650.html
《算法竞赛进阶指南》 李煜东 著
于HG机房