1. 程式人生 > >BSGS-Junior·大步小步算法

BSGS-Junior·大步小步算法

all problem 但是 har step 分塊 -i 歐拉 現在

本文原載於:http://www.orchidany.cf/2019/02/06/BSGS-junior/#more

\(\rm{0x01}\) \(\mathcal{Preface}\)

\(\rm{BSGS}(Baby~Step~Giant~Step)\), 大步小步法。當然也會被叫做拔山蓋世北上廣深算法……咳,這並不重要。形式化地講, \(\rm{BSGS}\)算法主要用來解決以下問題 :

給定質數\(p\), 整數\(a, b\), \((a, p)=1\).求最小的非負整數\(x\), 使得\(a^x≡ b~(\mod p)\)

而首先我們知道的,是由歐拉定理\(a ^{\varphi(p)} ≡ 1 ~(\mod p)?\)

,並且我們還知道\(a^0=1≡1 ~(\mod p)?\),所以我們可以得出一個斷言:

如果方程\(a^x≡ b~(\mod p)?\)有最小非負整數解,那麽最小非負整數解一定在\([0, \varphi(p))?\) \(\qquad \qquad(1) ?\)

此處肉眼可以看出其循環節為\(\varphi(p)\),不再證明。

之後我們將以此為基礎進行類似分塊的操作——

\(\rm{0x02~~Baby~Step~Giant~Step}\)

首先我們記\(n=\sqrt {\varphi(p)}\),那麽\(\forall x \in [0, \varphi(p))\), \(x = i\times m+j\)

, \(i \leq \lfloor \frac{p?1-m}{m} \rfloor,~~ 0≤j <m\) 。那麽對於原方程我們可以把其改為:\[a^{i\cdot n+j}≡ b~(\mod p)\]移一下項就可以變成\[a^j ≡b \cdot a^{-i\cdot n} (\mod p)\]那麽現在我們的策略是算出所有\(a^j\)來,在\(\mod p\) 意義下觀察是否有一個\(i\)使得\(a^j ≡b \cdot a^{-i\cdot n} (\mod p)\)。我們稱左邊枚舉\(a^j\)叫做小步\((\rm{Baby~Step})\), 稱右邊枚舉\(b \cdot a^{-i\cdot n}\)
叫做大步\(~(\rm{Giant~Step})\)

那麽其實算法流程很明晰了,我們只需要循環兩次、第一次記錄的\(a^j\)用哈希表(\(STL\)自帶\(unordered\)_ \(map\))記錄一下即可。

inline LL expow(LL a, LL b, LL p){
    LL res = 1 ;
    while (b){
        if (b & 1) 
            (res *= a) %= p ;
        (a *= a) %= p, b >>= 1 ;
    }
    return res % p ;
}
inline void bsgs(LL x, LL y, LL p){
    P = ceil(sqrt(p)), Hash.clear(), Q = expow(x, -P + 2 *(p - 1), p) ;
    //a ^ (p-1) = 1 (mod p) => Q = a^(-P) = a ^(-P + p -1) ;
    for (LL i = 1, j = 0 ; j < P ; ++ j, (i *= x) %= p) 
        if (!Hash.count(i)) Hash[i] = j ; // Push them into hash_table
    for (LL i = y, j = 0  ; j <= P ; ++ j, (i *= Q) %= p) 
        if (Hash.count(i)){ cout << Hash[i] + j * P << endl ; return ; }
    cout << "-1" << endl ;
}

其中細節還是有的:

  • 計算sqrt時要上取整

  • 我們在求\(a^{-i\cdot n}?\)時用的底變量需要由費馬小定理求快速冪得出。但是此時指數上可能為負數,所以我們選擇加上一個模數,不影響結果。
  • 兩次循環枚舉的邊界要註意有的是\(\leq\)有的是\(<\)
  • 算法還沒開始時,要判斷本身\(a\)是否可以被\(P\)整除。如果不特判這種情況的話,我們上面代碼中的Q就會=0,從而在下面的第二個循環處出錯——我們的hash[i]j不能同時為\(0\),從而輸出錯誤的答案。

\(\rm{0x03}\) 例題

\(T1~\)\(LuoguP4028\)

裸題,但是有很多坑……或者說上面列舉的細節都涵蓋了qaq

#include <cmath>
#include <cstdio>
#include <iostream>
#include<tr1/unordered_map>

#define LL long long

using namespace std ; 
using namespace tr1 ; int T ;
LL A, B, M, P, Q ; unordered_map <LL, LL> Hash ;

inline LL expow(LL a, LL b, LL p){
    LL res = 1 ;
    while (b){
        if (b & 1) 
            (res *= a) %= p ;
        (a *= a) %= p, b >>= 1 ;
    }
    return res % p ;
}
inline void bsgs(LL x, LL y, LL p){
    P = ceil(sqrt(p)), Hash.clear(), Q = expow(x, -P + 2 *(p - 1), p) ;
    //a ^ (p-1) = 1 (mod p) => Q = a^(-P) = a ^(-P + p -1) ;
    for (LL i = 1, j = 0 ; j < P ; ++ j, (i *= x) %= p) 
        if (!Hash.count(i)) Hash[i] = j ; // Push them into hash_table
    for (LL i = y, j = 0  ; j <= P ; ++ j, (i *= Q) %= p) 
        if (Hash.count(i)){ cout << Hash[i] + j * P << endl ; return ; }
    cout << "Couldn't Produce!" << endl ;
}
inline LL qr(){
    LL res = 0 ; char c = getchar() ; while (!isdigit(c)) c = getchar() ;
    while (isdigit(c)) res = (res << 1) + (res << 3) + c - 48, c = getchar() ;
    return res ;
}
int main(){
    cin >> T ;
    while (T --){
        M = qr(), A = qr(), B = qr() ;
        if ((!(A % M == 0 && B))) bsgs(A, B, M) ;
        else cout << "Couldn't Produce!" << endl ; 
    }
    return 0 ;
}

\(T2~\) \(TJOI2007~Cute~Prime?\)

最裸最裸的、無特判的題……可以水一下雙倍經驗。

\(\mathfrak{writter: pks}\)

BSGS-Junior·大步小步算法