BSGS算法(Baby Step Giant Step)
引子
我们之前学习了如何解这种方程\(x\equiv b_1\pmod{m_1}\)。
我们可以用扩展中国剩余定理或者欧拉定理
正文
问题
求最小的\(x\)使得\(a^x\equiv b\pmod{m},\gcd(a,m)=1\)
在没有学过BSGS北上广深之前,我们用暴力循环可以解决这一个问题。
时间复杂度\(O(\varphi(m))\)(提示:模的循环节)
但是如果\(m\)为质数的话\(\varphi(m)=m-1\)时间复杂度很高。
BSGS就是用来解决高次同余方程的。
解法
BSGS本质上就是一个优化后的暴力算法
我们构造一个\(x=At-B\),那么\(a^x=a^{At-B}\equiv b\pmod{m}\)
于是我们移项一下得到\(a^{At}\equiv ba^B\pmod{m}\)
求出\(A=\lceil\frac{x}{t}\rceil,B=x\%t\),推出\(B\in[0,t),A\in[0,\lceil\frac{x}{t}\rceil)\)
此时我们先预处理出\(ba^B\)的所有值,在一一比对。
此时我们构造\(t=\sqrt{\varphi(m)}\)使得时间复杂度为\(O(\sqrt{m})\)
在代码中我们直接取\(t=\sqrt(m)+1\)就可以了,身去了求\(\varphi\)的时间。
注意\(\gcd(a,m)=1\)
代码
P3846 [TJOI2007] 可爱的质数/【模板】BSGS
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll qpow(ll a, ll n, ll p) //快速幂
{
ll ans = 1;
a %= p;
while(n)
{
if(n & 1)
ans = ans * a % p;
a = a * a % p;
n >>= 1;
}
return ans;
}
ll BSGS(ll a, ll b, ll m)
{
//unordered_map<ll, ll> hs; //unordered_map是一个STL自带的哈希表
map<ll, ll> hs; //map 多一个 log
ll cur = b * a % m, t = sqrt(m) + 1; //向上取整
for (int B = 1; B <= t; B++)
{
hs[cur] = B; // 哈希表存B的值
cur = cur * a % m;
}
ll val = qpow(a, t, m);
cur = val; // a^t
for(int A = 1; A <= t; A++)
{
if(hs[cur])
return A * t - hs[cur];
cur = (cur * val) % m; //再乘一个a^t次方
}
return -1; // 无解
}
int main()
{
int p, b, n;
cin >> p >> b >> n;
int res = BSGS(b, n, p);
if(res == -1)
cout << "no solution";
else
cout << res;
return 0;
}
唯一要注意的是我们要特判\(y,p\)不互质的情况。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int T , K , y , z , p , ans;
inline int quickly_pow(int x , int y , int z){
int res = 1;
while(y != 0){
if(y % 2 == 1){
res = res * x % z;
}
x = x * x % z;
y >>= 1;
}
return res;
}
inline int BSGS(int a , int b , int m){
unordered_map <int , int> Hash;
int cur = a * b % m , t = sqrt(m) + 1;
for(int B = 1 ; B <= t ; B++ ){
Hash[cur] = B;
cur = cur * a % m;
}
int val = quickly_pow(a , t , m);
cur = val;
for(int A = 1 ; A <= t ; A++ ){
if(Hash[cur] != 0){
return A * t - Hash[cur];
}
cur = (cur * val) % m;
}
return -1;
}
inline int read(){
int s = 0 , w = 1;
char ch = getchar();
while((ch < '0') || (ch > '9')){
if(ch == '-'){
w = -1;
}
ch = getchar();
}
while((ch >= '0') && (ch <= '9')){
s = (s << 3) + (s << 1) + ch - '0';
ch = getchar();
}
return s * w;
}
inline void write(int x){
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9){
write(x / 10);
}
putchar(x % 10 + '0');
return ;
}
signed main(){
T = read();
K = read();
if(K == 1){
for(int i = 1 ; i <= T ; i++ ){
y = read();
z = read();
p = read();
printf("%lld\n" , quickly_pow(y , z , p));
}
}
if(K == 2){
for(int i = 1 ; i <= T ; i++ ){
y = read();
z = read();
p = read();
y %= p;
if(y == 0){
cout << "Orz, I cannot find x!" << endl;
}
else{
printf("%lld\n" , quickly_pow(y , p - 2 , p) * z % p);
}
}
}
if(K == 3){
for(int i = 1 ; i <= T ; i++ ){
y = read();
z = read();
p = read();
y %= p;
if(y == 0){
cout << "Orz, I cannot find x!" << endl;
}
else{
ans = BSGS(y , z , p);
if(ans == -1){
cout << "Orz, I cannot find x!" << endl;
}
else{
cout << ans << endl;
}
}
}
}
return 0;
}
扩展
如果是\(a^x\equiv b\pmod{m}\),\(a,m\)不互质了呢?
我们将\(a\)写成\(a=a_1\times g\),\(m=m_1\times g\)
同时将原方程转化成不定方程\(a\times a^{x-1}+my=b\)
因为\(a,m,b\)中都有\(g\)就可以转化为\((a/g)\times a^{x-1}+(m/g)y=b/g\)
我们又把它变成一个同余方程\((a/g)a^{x-1}\equiv b/g\pmod{m/g}\)
这样反复迭代就可以了
核心代码
ll BSGS(ll a, ll b, ll m, ll k = 1) // 带系数k的BSGS,默认为1
{
//unordered_map<ll, ll> hs; //unordered_map是一个STL自带的哈希表
map<ll, ll> hs; //map 多一个 log
ll cur = b * a % m, t = sqrt(m) + 1; //向上取整
for (int B = 1; B <= t; B++)
{
hs[cur] = B; // 哈希表存B的值
cur = cur * a % m;
}
ll val = qpow(a, t, m);
cur = val * k % m; // a^t
for(int A = 1; A <= t; A++)
{
if(hs[cur])
return A * t - hs[cur];
cur = (cur * val) % m; //再乘一个a^t次方
}
return -1000; //无解,返回一个小一点的负数,确保多次加1后仍然是负数
}
ll exBSGS(ll a, ll b, ll m, ll k = 1)
{
if(b == 1) // 特判1,x>0, 所以b==1,则x=0
return 0;
if(a == 0 && b == 0) // 特判2,底数为0,最小的x=1, 因为0^0 = 1
return 1;
ll d = gcd(a, m);
if(b % d != 0) // 无解, 斐蜀定理
return -1000;
else if(d == 1)
return BSGS(a, b, m, k % m); // 递归结束
return exBSGS(a, b / d, m / d, k * a / d % m) + 1; // 递归
}