BSGS大步小步算法
BSGS大步小步算法
用于解决离散对数问题:
已知 \(a^x≡b (mod\quad p)\),求 x 的最小非负整数解。其中 \(gcd(a,p)=1\)。
Small Step:对于 \(i ϵ [0,S)\) ,求出 \(a^i% p=A[i]\)
Big Step:对于 \(j ϵ [0,p/S]\) ,求出 \(a^{jS}% p=B[j]\)
这样如果 \(A[i]×B[j] ≡ b\) ( mod p) ,那么 \(x=jS+i\) 是合法解。
也即若对于 \(j ϵ [0,p/S]\) ,若存在 \(i ϵ [0,S)\) ,使得\(A[i]≡b×B[j]^{-1}\) (mod p) ,那么 \(x=jS+i\) 是合法解。
取 \(S≈\sqrt{p}\),则时间复杂度为\(O(\sqrt{p} logp)\)。
例1:New Product 板子,可爱的质数/【模板】BSGS
\[设u=\sqrt(p), A^x≡B(mod\quad p)可转化为 A^{iu-j}≡B(mod\quad p),iϵ[1,u],jϵ[0,u)\\
A^{iu}≡A^{j}B(mod\quad p)我们就可以枚举j,存到map中,再枚举i判重就行了\\
不过当存在不同的j使A^j≡b(mod\quad p)相同时,我们记录较大的(因为j越大答案越小嘛)\\
费马小定理:若gcd(x,p)=1,则有x^{p−1}≡1(mod\quad p),得到x^p≡x(mod\quad p)\\
所以当指数不小于p时,mod\quad p的值会形成循环\\
注意sqrt(p)必须上取整ceil()函数,否则sqrt(p)*sqrt(p)<p-1 漏了p-1这种情况
\]
#include <cstdio>
#include <iostream>
#include <cmath>
#include <map>
using namespace std;
#define int long long
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x;
}
map<int,int>mp;
int t,a,b,p;
int qpow(int a,int b,int p){
int ans=1;
while(b){
if(b&1) ans=(ans*a)%p;
a=(a*a)%p;
b>>=1;
}
return ans;
}
int BSGS(int a,int b,int p){
if(!a)return b?-1:1;
if(b==1)return 0;
if(a%p==0) return -1;
map<int,int>mp;
int u=ceil(sqrt(p)),ax=b;
for(int i=0;i<u;i++){
mp[ax]=i;
ax=ax*a%p;
}
int w=qpow(a,u,p),aj=1;
for(int i=1;i<=u;i++){
aj=aj*w%p;
if(mp.count(aj))return u*i-mp[aj];
}
return -1;
}
signed main(){
t=read();
while(t--){
mp.clear();//清空好习惯
p=read();a=read();b=read();
int ans=BSGS(a,b,p);
if(~ans) printf("%lld\n",ans);
else puts("Couldn't Produce!");
}
return 0;
}
例2:计算器
#include <cstdio>
#include <iostream>
#include <cmath>
#include <map>
using namespace std;
#define ll long long
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
return x;
}
map<int,int>mp;
int t,a,b,p,k;
ll qpow(ll a,ll b,int p){
ll ans=1;
while(b){
if(b&1) ans=(ans*a)%p;
a=(a*a)%p;
b>>=1;
}
return ans;
}
ll BSGS(ll a,ll b,ll p){
if(!a)return b?-1:1;
if(b==1)return 0;
if(a%p==0) return -1;
map<ll,ll>mp;
ll u=ceil(sqrt(p)),ax=b;
for(int i=0;i<u;i++){
mp[ax]=i;
ax=ax*a%p;
}
ll w=qpow(a,u,p),aj=1;
for(int i=1;i<=u;i++){
aj=aj*w%p;
if(mp.count(aj))return u*i-mp[aj];
}
return -1;
}
int main(){
t=read();k=read();
if(k==1){
for(int i=1;i<=t;i++){
a=read();b=read();p=read();
printf("%lld\n",qpow(a,b,p));
}
}else if(k==2){
for(int i=1;i<=t;i++){
a=read();b=read();p=read();
if(a%p==0) printf("Orz, I cannot find x!\n");
else printf("%lld\n",qpow(a,p-2,p)*b%p);
}
}else{
for(int i=1;i<=t;i++){
a=read();b=read();p=read();
ll ans=BSGS(a%p,b%p,p);
if(~ans) printf("%lld\n",ans);
else printf("Orz, I cannot find x!\n");
}
}
return 0;
}
拓展BSGS
\[若gcd(a,p)≠1,令d=gcd(a,p),将方程改写成等式形式,a^x+kp=b;\\
此时 必须满足b|d,同时除以d 得到\frac{a}{d}*a^{x-1}+\frac{k}{d}*p=\frac{b}{d}\\
这样前面的\frac{a}{d}就是一个系数了,不断检查gcd(\frac{b}{d},a),一直除到互质为止
此时的形式就变成了(\frac{a}{d})^k*a^{x-k}≡\frac{b}{d}(mod\quad \frac{p}{d})\\
这样子bsgs求解之后在还原回去就行了。
\]
好吧下面是正解,其实就多了这一步
while(d!=1){
if(b%d)return -1;
p/=d;b/=d;++k;
c=1ll*c*(a/d)%p;
if(b==c) return k;
d=gcd(a,p);
}
注意aj = c;
还要特判 if(b==c) 的情况
#include <cstdio>
#include <iostream>
#include <cmath>
#include <map>
using namespace std;
#define int long long
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
map<int,int>mp;
int t,a,b,p;
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
int qpow(int a,int b,int p){
int ans=1;
while(b){
if(b&1) ans=(ans*a)%p;
a=(a*a)%p;
b>>=1;
}
return ans;
}
int ex_BSGS(int a,int b,int p){
if(!a) return b?-1:1;
if(b==1||p==1)return 0;
if(a%p==0) return -1;
int d=gcd(a,p),k=0,c=1;
while(d!=1){
if(b%d)return -1;
p/=d;b/=d;++k;
c=1ll*c*(a/d)%p;
if(b==c) return k;
d=gcd(a,p);
}
mp.clear();
int u=ceil(sqrt(p)),ax=b;
for(int i=0;i<u;i++){
mp[ax]=i;
ax=1ll*ax*a%p;
}
int w=qpow(a,u,p),aj=c;
for(int i=1;i<=u;i++){
aj=1ll*aj*w%p;
if(mp.count(aj)) return k+u*i-mp[aj];
}
return -1;
}
signed main(){
while(1){
a=read();p=read();b=read();
if(!p && !a && !b) return 0;;
int ans=ex_BSGS(a%p,b%p,p);
if(~ans) printf("%lld\n",ans);
else puts("No Solution");
}
}
例3:多少个1?
Desciption
给定整数 K和质数 m,求最小的正整数 N,使得 11111⋯1(N个 1)≡K(mod m)
Solution
\[N个1 可以转成等比数列求和 1,10,100....即 \frac{10^n-1}{9}≡K(mod\quad m)\\
移项 10^n≡9*K+1(mod\quad m) ,令a=10,b=9*K+1,p=m,套exBSGS板子可过~~不去~~\\
额乘法爆long long,题解的巧妙方法,快速乘
\]
#include <iostream>
#include <cstdio>
#include <map>
#include <cmath>
using namespace std;
#define int long long
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int mul(int a, int b, int P){//快速乘
int L = a * (b >> 25ll) % P * (1ll << 25) % P;
int R = a * (b & ((1ll << 25) - 1)) % P;
return (L + R) % P;
}
int gcd(int a,int b){
if(a<b) swap(a,b);
return b?gcd(b,a%b):a;
}
int qpow(int a,int b,int p){
int ans=1;
while(b){if(b&1)ans=mul(ans,a,p);a=mul(a,a,p);b>>=1;}
return ans;
}
map<int,int>mp;
int ex_BSGS(int a,int b,int p){
if(!a) return b?-1:1;
if(b==1|p==1) return 0;
if(a%p==0) return -1;
int d=gcd(a,p),c=1,k=0;
while(d!=1){
if(b%d) return -1;
b/=d;p/=d;++k;
c=mul(c,a/d,p);
if(b==c) return k;
d=gcd(a,p);
}
mp.clear();
int u=ceil(sqrt(p)),ax=b;
for(int i=0;i<u;i++)
mp[ax]=i,ax=mul(ax,a,p);
int aj=c,w=qpow(a,u,p);
for(int i=1;i<=u;i++){
aj=mul(aj,w,p);
if(mp.count(aj)) return i*u-mp[aj]+k;
}
return -1;
}
signed main(){
int k=read(),p=read();
printf("%lld\n",ex_BSGS(10,(9*k+1)%p,p));
}