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,简称 B S G S BSGS BSGS),可以在 O ( l o g P ) O(logP) O(logP)的时间内求出 b a s e x ≡ n ( m o d    P ) base^x\equiv n(mod\;P) basex≡n(modP)的解 x x x,也就是在模 P P P意义下,以 b a s e base base为底, n n n的离散对数。
其中要求 ( b a s e , P ) = 1 (base,P)=1 (base,P)=1。
过程
其实就是一个暴力而已,假定已知
m
m
m,将
x
x
x分解成
b
m
−
a
bm-a
bm−a的形式,有:
b
a
s
e
b
m
≡
n
∗
b
a
s
e
a
(
m
o
d
  
P
)
base^{bm}\equiv n*base^{a}(mod\;P)
basebm≡n∗basea(modP)
我们枚举
a
∈
[
0
,
m
−
1
]
a\in[0,m-1]
a∈[0,m−1],将
n
∗
b
a
s
e
a
%
P
n*base^{a}\%P
n∗basea%P打标记。然后再枚举
b
∈
[
1
,
P
/
m
]
b\in[1,P/m]
b∈[1,P/m],判断
b
a
s
e
b
m
%
P
base^{bm}\%P
basebm%P是否被打标记,如果是,则说明找到了一对
<
a
,
b
>
<a,b>
<a,b>,带入得
b
m
−
a
bm-a
bm−a就是答案。
显然,当 m m m取 P \sqrt P P时 a , b a,b a,b的枚举次数期望最少。此时有 b ∈ [ 1 , m + 1 ] b\in[1,m+1] b∈[1,m+1]。
判断标记最好使用哈希链表,不然时间复杂度堪忧。
例题
original link - http://poj.org/problem?id=2417
/*
* Author : Jk_Chen
* Date : 2019-08-20-19.42.31
*/
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
using namespace std;
#define LL long long
#define rep(i,a,b) for(int i=(int)(a);i<=(int)(b);i++)
#define per(i,a,b) for(int i=(int)(a);i>=(int)(b);i--)
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define pill pair<int, int>
#define fi first
#define se second
const LL mod=1e9+7;
const int maxn=1e5+9;
LL rd(){ LL ans=0; char last=' ',ch=getchar();
while(!(ch>='0' && ch<='9'))last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans; return ans;
}
/*_________________________________________________________head*/
LL Pow(LL a,LL b,LL mod){
LL res=1;
while(b>0){
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
namespace Hash{
const int Mod=102023;
int head[Mod],nex[Mod],now;
pill val[Mod];
void init(){
now=0;
mmm(head,0);
}
void insert(int x,int y){
int key=x%Mod;
nex[++now]=head[key];head[key]=now;
val[now].fi=x,val[now].se=y;
}
int find(int x){
int key=x%Mod;
for(int i=head[key];i;i=nex[i]){
if(val[i].fi==x)return val[i].se;
}
return -1;
}
}
void BSGS(int base,int n,int p){ // Pow(base, b*m) = n * Pow(base, a)
if(n==1){
printf("0\n");return;
}
Hash::init();
// x = a + bm
int m=sqrt(p);
int tmp=n;
rep(a,0,m-1){
Hash::insert(tmp,a);
tmp=1ll*tmp*base%p;
}
tmp=1;
LL mul=Pow(base,m,p);
rep(b,1,m+1){
tmp=1ll*tmp*mul%p;
int a=Hash::find(tmp);
if(~a){
printf("%d\n",b*m-a);
return;
}
}
printf("no solution\n");
}
int main(){
int base,n,p;
while(cin>>p>>base>>n){
BSGS(base,n,p);
}
return 0;
}
同余定理
Extended BSGS
B S G S BSGS BSGS仅适用于 ( b a s e , P ) = 1 (base,P)=1 (base,P)=1的情况,对于非互质情况,需要用扩展算法进行转化。
我们提取
d
=
(
b
a
s
e
,
P
)
d=(base,P)
d=(base,P),原式
b
a
s
e
x
≡
n
(
m
o
d
  
P
)
→
base^x\equiv n(mod\;P)\to
basex≡n(modP)→
b
a
s
e
x
−
1
∗
b
a
s
e
d
≡
n
d
(
m
o
d
  
P
d
)
base^{x-1}*\dfrac{base}{d}\equiv \dfrac{n}{d}(mod\;\dfrac{P}{d})
basex−1∗dbase≡dn(moddP)
这里的
(
n
,
d
)
=
̸
d
(n,d)=\not d
(n,d)≠d时,根据同余定理可判断方程无解。
继续上述过程直至
(
b
a
s
e
,
P
)
=
1
(base,P)=1
(base,P)=1,有:
b
a
s
e
x
−
k
∗
b
a
s
e
d
1
.
.
d
k
≡
n
d
1
.
.
d
k
(
m
o
d
  
P
d
1
.
.
d
k
)
base^{x-k}*\dfrac{base}{d_1..d_k}\equiv \dfrac{n}{d_1..d_k}(mod\;\dfrac{P}{d_1..d_k})
basex−k∗d1..dkbase≡d1..dkn(modd1..dkP)
因为模数底数互质,所以可以直接对此式进行
B
S
G
S
BSGS
BSGS。
b
a
s
e
b
m
−
a
∗
b
a
s
e
d
1
.
.
d
k
≡
n
d
1
.
.
d
k
(
m
o
d
  
P
d
1
.
.
d
k
)
base^{bm-a}*\dfrac{base}{d_1..d_k}\equiv \dfrac{n}{d_1..d_k}(mod\;\dfrac{P}{d_1..d_k})
basebm−a∗d1..dkbase≡d1..dkn(modd1..dkP)
b
a
s
e
b
m
∗
b
a
s
e
d
1
.
.
d
k
≡
n
d
1
.
.
d
k
∗
b
a
s
e
a
(
m
o
d
  
P
d
1
.
.
d
k
)
base^{bm}*\dfrac{base}{d_1..d_k}\equiv \dfrac{n}{d_1..d_k}*base^{a}(mod\;\dfrac{P}{d_1..d_k})
basebm∗d1..dkbase≡d1..dkn∗basea(modd1..dkP)
最后的答案为 b m − a + k bm-a+k bm−a+k。
例题
original link - http://poj.org/problem?id=3243
/*
* Author : Jk_Chen
* Date : 2019-08-21-09.12.01
*/
#include<iostream>
#include<math.h>
#include<string.h>
#include<algorithm>
#include<stdio.h>
using namespace std;
#define LL long long
#define rep(i,a,b) for(int i=(int)(a);i<=(int)(b);i++)
#define per(i,a,b) for(int i=(int)(a);i>=(int)(b);i--)
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define pill pair<int, int>
#define fi first
#define se second
const LL mod=1e9+7;
const int maxn=1e5+9;
LL rd(){ LL ans=0; char last=' ',ch=getchar();
while(!(ch>='0' && ch<='9'))last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans; return ans;
}
/*_________________________________________________________head*/
LL Pow(LL a,LL b,LL mod){
LL res=1;
while(b>0){
if(b&1)res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
namespace Hash{
const int Mod=102023;
int head[Mod],nex[Mod],now;
pill val[Mod];
void init(){
now=0;
mmm(head,0);
}
void insert(int x,int y){
int key=x%Mod;
nex[++now]=head[key];head[key]=now;
val[now].fi=x,val[now].se=y;
}
int find(int x){
int key=x%Mod;
for(int i=head[key];i;i=nex[i]){
if(val[i].fi==x)return val[i].se;
}
return -1;
}
}
int BSGS(int base,int n,int p){ // Pow(base, b*m) * bd = n * Pow(base, a)
if(n==1){
return 0;
}
int d=__gcd(base,p);
int bd=1,k=0;
while(d>1){
if(n%d){
return -1;
}
n/=d;
p/=d;
k++;
bd=1ll*bd*(base/d)%p;
if(bd==n){
return k;
}
d=__gcd(base,p);
}
Hash::init();
int m=sqrt(p);
int tmp=n;
rep(a,0,m-1){
Hash::insert(tmp,a);
tmp=1ll*tmp*base%p;
}
tmp=bd;
LL mul=Pow(base,m,p);
rep(b,1,m+1){
tmp=1ll*tmp*mul%p;
int a=Hash::find(tmp);
if(~a){
return b*m+k-a;
}
}
return -1;
}
int main(){
int base,n,p;
while(cin>>base>>p>>n,base|p|n){
int res=BSGS(base,n,p);
if(~res)cout<<res<<endl;
else puts("No Solution");
}
return 0;
}