【算法】BSGS算法的推導與實現

BSGS簡介

BSGS算法,全稱Baby Step Giant Step算法,用於求解關於xx的形如axb(modp)a^{x} \equiv b \pmod ppp爲質數的方程。

求解過程

不妨設x=imjx=im-j​,其中m=pm=\lceil\sqrt{p}\rceil​j[0,m)j \in [0,m)​i[1,m]i \in [1,m]​。於是原方程轉化爲:
aimjb(modp) a^{im-j} \equiv b \pmod p
繼續轉化:
aimajb(modp) a^{im} \equiv a^{j}b \pmod p
於是我們可以枚舉jj​,並將ajbmod  pa^{j}b \mod p​的答案記錄在map中(當然如果你是手寫哈希表的巨佬,那就當我沒說)。map中的key存的是答案,value存的是jj​

然後我們再枚舉ii,在map中查找aimmod  pa^{im}\mod p,如果查詢到了,那麼查詢到的即爲jj,那麼x=imjx=im-j即爲答案。


一些證明

注意到我們求解時取了m=pm=\lceil\sqrt{p}\rceil​,這能保證我們枚舉的時間複雜度均爲O(p)O(\sqrt p)​,但是卻限定了答案x[0,p]x \in [0,p]​,那麼如何保證解一定在這個範圍內呢?

證明過程需要費馬小定理:ap11(modp)a^{p-1} \equiv 1 \pmod p​,其中pp​爲質數,gcd(a,p)=1gcd(a,p)=1​

引理

akmod  (p1)ak(modp) a^{k\mod(p-1)}\equiv a^{k} \pmod p

條件與費馬小定理相同。

證明:我們可以把kmod  (p1)k\mod (p-1)看做kn(p1)k-n(p-1),那麼原方程化爲:
ak(ap1)nak(modp) \frac{a^{k}}{(a^{p-1})^{n}} \equiv a^k \pmod p
通過費馬小定理,可知ap11(modp)a^{p-1} \equiv 1 \pmod p,所以原式顯然成立。

正式的證明

其實知道引理後直接一個顯然就好啦

由引理可知,即使在(p,+)(p,+\infty)中存在解,我們依舊可以通過mod  (p1)\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;
}

例題(水經驗)

洛谷2485 [SDOI2011]計算器

前兩個操作是快速冪和擴歐的模板,第三個操作是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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章