【算法讲25:BSGS】
- 引入
- BSGS
- 代码
- 扩展BSGS
- ExBSGS
- 递归版代码
- 非递归版
- yjw的博客入口
引入
-
P3846 [TJOI2007] 可爱的质数/【模板】BSGS
给定一个质数 p p p 以及整数 a 、 b a、b a、b
你需要给出最小的非负整数 x x x,满足:
a x ≡ b ( m o d p ) a^x\equiv b\pmod p ax≡b(modp)
或输出无解。
- 2 ≤ a , b < p < 2 31 2\le a,b<p<2^{31} 2≤a,b<p<231
BSGS
-
全称为 B a b y S t e p G i a n t S t e p Baby\ Step\ Giant\ Step Baby Step Giant Step,简称BSGS或者北上广深算法。
怎么做呢?其实本质是枚举
- 最原始的做法:暴力枚举 x ∈ [ 0 , p ) x\in[0,p) x∈[0,p) 然后检验答案是否正确。
-
优化,我们可以考虑折半枚举。
我们令 x = k n + i x=kn+i x=kn+i,这里 n n n 是我们设的一个定值, k 、 i k、i k、i 都为变量。
这样,原式变成: a k n + i ≡ b ( m o d p ) a^{kn+i}\equiv b\pmod p akn+i≡b(modp)
由于 gcd ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1,我们转移一下变成: a k n ≡ b ( a i ) − 1 ( m o d p ) a^{kn}\equiv b(a^i)^{-1}\pmod p akn≡b(ai)−1(modp)
-
我们可以枚举所有的 i i i ,将右边的值存入哈希表。
然后枚举 k k k 的过程中,去表中找满足的 i i i,找到后 k n + i kn+i kn+i 就是答案。
-
那么 n n n 的设置,就像分块思想一样,我们设为 p \sqrt p p
,这样 k 、 i k、i k、i 都只用枚举到 p \sqrt p p
代码
-
时间复杂度: O ( p ) O(\sqrt p) O(p
)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
const int MAX = 30;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll exgcd(ll a,ll b,ll& x,ll& y){
if(b==0){
x=1;y=0;
return a;
}
ll ans = exgcd(b,a%b,x,y);
ll tmp = x;
x = y;
y = tmp - a/b*y;
return ans;
}
ll inv(ll a,ll mod){
ll x,y;
ll g = exgcd(a,mod,x,y);
if(g!=1)return -1;
return (x%mod+mod)%mod;
}
ll BSGS(ll a,ll b,ll p){
b%=p;
if(b==1||p==1)return 0;
ll n = sqrt(p);
unordered_map<ll,ll>M;
ll inva = inv(qpow(a,n-1,p),p) * b % p;
for(ll i = n-1;~i;--i){
M[inva] = i;
inva = inva * a % p;
}
ll ta = 1,powa = qpow(a,n,p);
for(ll k = 0;k <= p;k+=n){
if(M.count(ta))return k + M[ta];
ta = ta * powa % p;
}
return -1;
}
int main()
{
ll p,b,n,ans;
cin >> p >> b >> n;
ans = BSGS(b,n,p);
if(ans == -1)puts("no solution");
else cout << ans;
return 0;
}
扩展BSGS
-
P4195 【模板】扩展 BSGS/exBSGS
给定整数 a 、 b 、 p a、b、p a、b、p
你需要给出最小的非负整数 x x x,满足:
a x ≡ b ( m o d p ) a^x\equiv b\pmod p ax≡b(modp)
或输出无解。
- 1 ≤ a , b , p < 1 0 9 1\le a,b,p<10^9 1≤a,b,p<109
ExBSGS
-
因为 gcd ( a , p ) ≠ 1 \gcd(a,p)\ne 1 gcd(a,p)=1,所以不能像原来一样使用逆元了。
所以我们想把 g c d gcd gcd 先提出来。我们令 d = gcd ( a , p ) d=\gcd(a,p) d=gcd(a,p)
-
我们把原式 a x ≡ b ( m o d p ) a^x\equiv b\pmod p ax≡b(modp) 展开成 a x + k p = b a^x+kp=b ax+kp=b
两边同时除以 d d d,变成 a x d + k p d = b d \frac{a^x}{d}+k\frac{p}{d}=\frac{b}{d} dax+kdp=db
取模,得到: a x d ≡ b d ( m o d p d ) \frac{a^x}{d}\equiv \frac{b}{d}\pmod {\frac{p}{d}} dax≡db(moddp)
发现, b b b 必须是 d d d 的倍数,否则直接返回无解。
此时 gcd ( a d , p d ) = 1 \gcd(\frac{a}{d},\frac{p}{d})=1 gcd(da,dp)=1,所以就可以乘上它的逆元
变成: a x − 1 ≡ b d ( a d ) − 1 ( m o d p d ) a^{x-1}\equiv \frac{b}{d}(\frac{a}{d})^{-1}\pmod {\frac{p}{d}} ax−1≡db(da)−1(moddp)
然后就变成了一个新的问题。但是此时 gcd ( a , p d ) \gcd(a,\frac{p}{d}) gcd(a,dp) 不一定为 1 1 1,可以递归处理。(当然也可以不递归处理)
- 注意就是BSGS里面的 u n o r d e r e d _ m a p unordered\_map unordered_map 改成 m a p map map 在这道题里会快很多,因为出题人特意卡了一个样例…
递归版代码
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
const int MAX = 30;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll exgcd(ll a,ll b,ll& x,ll& y){
if(b==0){
x=1;y=0;
return a;
}
ll ans = exgcd(b,a%b,x,y);
ll tmp = x;
x = y;
y = tmp - a/b*y;
return ans;
}
ll inv(ll a,ll mod){
ll x,y;
ll g = exgcd(a,mod,x,y);
if(g!=1)return -1;
return (x%mod+mod)%mod;
}
ll BSGS(ll a,ll b,ll p){
b%=p;
if(b==1||p==1)return 0;
ll n = sqrt(p);
unordered_map<ll,ll>M;
ll inva = inv(qpow(a,n-1,p),p) * b % p;
for(ll i = n-1;~i;--i){
M[inva] = i;
inva = inva * a % p;
}
ll ta = 1,powa = qpow(a,n,p);
for(ll k = 0;k <= p;k+=n){
if(M.count(ta))return k + M[ta];
ta = ta * powa % p;
}
return -1;
}
ll ExBSGS(ll a,ll b,ll p){
b%=p;
if(a==0 && b==0)return 1;
if(a==0 && b!=0)return -1;
if(b==1||p==1)return 0;
ll d = __gcd(a,p);
if(b%d!=0)return -1;
p/=d;
b=b/d*inv(a/d,p)%p;
if(d!=1){
ll ans = ExBSGS(a,b,p);
if(ans==-1)return -1;
return ans+1;
}
ll ans = BSGS(a,b,p);
if(ans == -1)return -1;
return ans+1;
}
int main()
{
ll a,b,p,ans;
while(~scanf("%lld%lld%lld",&a,&p,&b)){
if(p == 0)break;
ans = ExBSGS(a,b,p);
if(ans == -1)puts("No Solution");
else printf("%lld\n",ans);
}
return 0;
}
非递归版
ll ExBSGS(ll a,ll b,ll p){
b%=p;
if (b==1||p==1)return 0;
int g=__gcd(a,p),k=0,na=1;
while (g>1)
{
if (b%g!=0)return -1;
k++;b/=g;p/=g;na=na*(a/g)%p;
if (na==b)return k;
g=__gcd(a,p);
}
int f=BSGS(a,b*inv(na,p)%p,p);
if (f==-1)return -1;
return f+k;
}