定义:
Baby-Step-Giant-Step算法,简称BSGS算法,又称大步小步算法,用于求方程ax ≡b(mod c)的最小非负整数解,此过程又称离散对数。
普通BSGS算法只能解决c为质数的情况,对其进行扩展之后则没有这个限制。
原理:
当c为质数时,若a%c==0且b!=0,则显然无解。若a%c!=0,有ac-1 ≡ a0 ≡1(mod c),即c-1是一个循环节。因此如果方程有解,必定在0~c-2中。
因此得到一个朴素的算法,枚举0~c-2中每一个数,快速幂判断,如果相等就输出答案,时间复杂度O(c)。考虑对其进行优化,这时我们引入分块的思想。
设x=i*m+j(0≤j<m),原方程写成ai*m *aj ≡b(mod c)。令ai*m =D(i),原方程变为D(i)*aj ≡b(mod c)。
暴力枚举每一个可能的i,用扩展欧几里得可以求出aj 。可是我们依然无法求得j。此时注意到j的所有可能取值只有m个,因此我们预先将所有的aj 存入一个哈希表,当我们确定aj 时,直接查表可以O(1)得到是否有对应的j,以及对应的j是多少。暴力枚举复杂度O(i),预处理复杂度O(m),总复杂度O(i+m)。注意到x≈i*m,因此当m取c½ 时,总复杂度最小,为O(c½)。
至此,我们采用以空间换时间,最后通过分块的思想将该算法优化至根号级别。
代码:
#define LL long long
void bsgs(LL a,LL b,LL c)
{
LL i,j,m=ceil(sqrt(c)),tmp,base,x;
if(a==0&&b!=0){cout<<"Orz, I cannot find x!\n";return;}//特判(求逆元的方法不同,0没有逆元)
for(base=1,i=0;i<m;i++)//预处理:将所有的aj 存入哈希表
{
insert(base,i);
base=base*a%c;
}
for(tmp=1,i=0;i<m;i++)
{
x=pow_mod(tmp,c-2,c)*b%c;//类似扩展欧几里得
j=find(x);if(j!=-1){printf("%lld\n",i*m+j);return;}//若不存在j返回-1
tmp=tmp*base%c;//此时base的值就是am
}
cout<<"Orz, I cannot find x!\n";//输出无解
}
例题:
SDOI2011 计算器 题目链接
题目要求:你被要求设计一个计算器完成以下三项任务:
1、给定y、z、p,计算y^z mod p 的值;
2、给定y、z、p,计算满足xy ≡z(mod p)的最小非负整数x;
3、给定y、z、p,计算满足y^x ≡z(mod p)的最小非负整数x。
数据范围:多组数据,1≤y,z,p≤1e9,p为质数,数据组数T≤10。
题解:
操作1:快速幂;操作2:扩展欧几里得;操作3:普通BSGS。然而我没看见p为质数拿扩展BSGS做了好久......
代码略
扩展:
讲道理现在还不是很理解扩展之后的一系列操作......大概目前除了背之外并没有太好的办法,先把代码放上吧。
代码:
void exgcd(LL a,LL b,LL &x,LL &y)
{
LL p,q,u,v;
if(!b){x=1;y=0;return;}
p=a/b;q=a%b;
exgcd(b,q,u,v);
x=v;y=u-p*v;
}
void exbsgs(LL a,LL b,LL c)
{
LL i,j,cnt=0,m=ceil(sqrt(c)),tmp,d=1,x,y,base;
for(tmp=1,i=0;i<32;i++){if(tmp==b){printf("%lld\n",i);return;}tmp=tmp*a%c;}
for(;(tmp=gcd(a,c))!=1;)
{
cnt++;
if(b%tmp){cout<<"Orz, I cannot find x!\n";return;}
b/=tmp;c/=tmp;d=d*a/tmp%c;
}
//for(tmp=1,i=0;i<cnt;i++){if(tmp==b){printf("%lld\n",i);return;}tmp=tmp*a%c;}
for(base=1,i=0;i<m;i++)
{
insert(base,i);
base=base*a%c;
}
//j=-1;
for(i=0;i<m;i++)
{
exgcd(d,c,x,y);tmp=gcd(d,c);
x=(x*b/tmp%c+c)%c;
j=find(x);
if(j!=-1){printf("%lld\n",i*m+j+cnt);return;}
d=d*base%c;
}
cout<<"Orz, I cannot find x!\n";
}