Baby Steps Giant Steps(BSGS)及其扩展——杨子曰算法

Baby Steps Giant Steps(BSGS)及其扩展——杨子曰算法

超链接:数学合集


又名巴士公司,北上广深,拔山盖世……
感叹:中华汉字真是博大精深啊!


BSGS

他可以干嘛捏?
解方程: a x ≡ b   ( m o d   p ) a^x\equiv b\ (mod\ p) axb (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 313329337341353
欧,有没有发现 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) 3xb(mod 10)
这个b的取值只有4个

那么我们能不能再把这个事情稍稍扩展一下,变成:
a x ≡ b ( m o d   p ) a^x \equiv b(mod\ p) axb(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=ax1 mod px mod p

So,当两次模完p以后值一样时,就产生循环节了

现在我们再来看一下我们最终要解决的问题: a x ≡ b   ( m o d   p ) a^x\equiv b\ (mod\ p) axb (mod p)的最小非负整数解,a和p互质

由于左边那个东西会产生循环节,而我们要得到的也只要是x的最小非负整数解,那我们是不是只要考虑循环节里能取到的值那不能等于b,然后输出对应的x就好了

那么接下来我们就来考虑一下这个循环节最长会有多长?

我们可以用两个办法来思考这个问题:

  1. 前面已经说过了,如果两个 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个数)
  2. 首先: a 0 ≡ 1 (   m o d   p ) a^0\equiv 1(\ mod\ p) a01( 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=itj,那么i在[0,t]里,j在[0,t)里,这样一定可以覆盖所有情况,那么方程就是:
a i ∗ t − j ≡ b ( m o d   p ) a^{i*t-j} \equiv b(mod\ p) aitjb(mod p)
于是就变成了这样:
( a t ) i ≡ b ∗ a j ( m o d   p ) (a^t)^i \equiv b*a^j(mod\ p) (at)ibaj(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}) daxdb(mod dp)
把一个 a x − 1 a^{x-1} ax1拎出来:
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}) ax1dadb(mod dp)

现在想象一下,如果我们把这个 a d \frac{a}{d} da除到等号右边的话(程序中先不要在这里移项),方程是不是就变回原来的格式了!

原来的 a x a^x ax变成了 a x − 1 a^{x-1} ax1,p变成了 p d \frac{p}{d} dp

肯定有人会说了: a x − 1 a^{x-1} ax1 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}) axkdiakdib(mod dip)

现在我们把 a k ∏ d i \frac{a^k}{\prod d_i} diak移过去(这里要使用逆元了,不会的童鞋戳我)

方程就是原来的格式了,而且这个时候 a x − k a^{x-k} axk 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机房

你可能感兴趣的:(变态的算法,崩溃的数学)