BSGS-Junior·大步小步算法
本文原載於: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^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\)
那麽其實算法流程很明晰了,我們只需要循環兩次、第一次記錄的\(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·大步小步算法