BSGS簡介
BSGS算法,全稱Baby Step Giant Step算法,用於求解關於的形如 ,爲質數的方程。
求解過程
不妨設,其中,,。於是原方程轉化爲:
繼續轉化:
於是我們可以枚舉,並將的答案記錄在map中(當然如果你是手寫哈希表的巨佬,那就當我沒說)。map中的key存的是答案,value存的是。
然後我們再枚舉,在map中查找,如果查詢到了,那麼查詢到的即爲,那麼即爲答案。
一些證明
注意到我們求解時取了,這能保證我們枚舉的時間複雜度均爲,但是卻限定了答案,那麼如何保證解一定在這個範圍內呢?
證明過程需要費馬小定理:,其中爲質數,。
引理
條件與費馬小定理相同。
證明:我們可以把看做,那麼原方程化爲:
通過費馬小定理,可知,所以原式顯然成立。
正式的證明
其實知道引理後直接一個顯然就好啦
由引理可知,即使在中存在解,我們依舊可以通過操作在$ [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;
}
例題(水經驗)
前兩個操作是快速冪和擴歐的模板,第三個操作是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;
}