【概述】
BSGS(Baby Step Giant Step)算法,又称大小步算法,其主要用于解形如 的高次同余方程中的 x,其核心思想是分块。
当 A 与 C 互质时,通过费马小定理:
可知,当 时,会出现一个循环节,于是就能保证答案 x 若存在,必然有
因此,当 C 比较小时,可使用暴力,直接令从 0 枚举到 C-1,检验其是否为方程的解,而当 C 比较大时,使用暴力会 TLE,此时可以采用 BSGS 算法来求解 x,其时间复杂度是 级别的
朴素的 BSGS 算法只能处理 C 是质数的情况,扩展的 BSGS 通过同余性质消因子来解决 C 不是质数的情况。
【BSGS 算法】
1.算法思想
根据分块思想,设置一个变量 ,此时使用 ceil() 函数向上取整,可以保证 ,从而避免遗漏答案。
不难发现,此时 x 可表示为:
那么 就转为
移项,得:
此时对左边的 j 从 0 枚举到 size-1,将 加入 Hash 表中(这一步是 Baby Step)
再对右边进行枚举 (这一步是 Giant Step),然后再从 Hash 表中查找是否有这个值,若有的话,则得到一组 (i,j),那么可得到正确解:
2.实现
struct HashMap{//哈希表
static const int Hash=999917,maxn=46340;
int num,link[Hash],son[maxn+5],next[maxn+5],w[maxn+5];
int top,Stack[maxn+5];
void clear(){//清空表
num=0;
while(top)
link[Stack[top--]]=0;
}
void add(int x,int y){//添加键值元素
son[++num]=y;
next[num]=link[x];
w[num]=INF;
link[x]=num;
}
bool count(int y){//判断表中是否有对应值
int x=y%Hash;
for(int j=link[x];j;j=next[j])
if(y==son[j])
return true;
return false;
}
int &operator [](int y){//获取键的对应值
int x=y%Hash;
for(int j=link[x];j;j=next[j])
if(y==son[j])
return w[j];
add(x,y);
Stack[++top]=x;
return w[num];
}
}hashMap;
int extendedGCD(int x,int y,int &a,int &b){
if(y==0){
a=1;
b=0;
return x;
}
int temp;
int gcd=extendedGCD(y,x%y,a,b);
temp=a;
a=b;
b=temp-x/y*b;
return gcd;
}
int BSGS(int A,int B,int C){
//三种特判
if(C==1){
if(!B)
return A!=1;
return -1;
}
if(B==1){
if(A)
return 0;
return -1;
}
if(A%C==0){
if(!B)
return 1;
return -1;
}
hashMap.clear();
int Size=ceil(sqrt(C)),D=1,Base=1;
for(int i=0;i<=Size-1;i++){//将A^j存进哈希表
hashMap[Base]=min(hashMap[Base],i);//存储最小的
Base=((LL)Base*A)%C;
}
for(int i=0;i<=Size-1;i++){//扩展欧几里得求A^j
int x,y;
int gcd=extendedGCD(D,C,x,y);//求出x、y
x=((LL)x*B%C+C)%C;
if(hashMap.count(x))//找到答案
return i*Size+hashMap[x];
D=((LL)D*Base)%C;
}
return -1;//无解
}
int main(){
int A,B,C;
while(scanf("%d%d%d",&A,&B,&C)!=EOF&&(A+B+C)){
int res=BSGS(A,B,C);
if(res==-1)
printf("No Solution\n");
else
printf("%d\n",res);
}
return 0;
}
【扩展 BSGS 算法】
1.算法思想
对于 C 不是质数的情况, 等价于
对其进行消因子处理,使得 化为 使得 C' 与 A 不再有可以约的因子:cnt=x-x',每次消因子过程中,方程右边同时消除一样的因子
将 作为一个整体,利用扩展欧几里得得到其解系,假设得到的一组原始特解为 ,则:
化为高次同余方程:,此时可利用 BSGS 求解得到 ,加回 cnt 即可得到原方程的解
需要注意的是,这样得到的只有不大于 cnt 的解,可能会漏掉小于 cnt 的解,因此一般先从 1~log(50) 查找一遍
2.实现
struct HashMap{//哈希表
static const int Hash=999917,maxn=46340;
int num,link[Hash],son[maxn+5],next[maxn+5],w[maxn+5];
int top,Stack[maxn+5];
void clear(){//清空表
num=0;
while(top)
link[Stack[top--]]=0;
}
void add(int x,int y){//添加键值元素
son[++num]=y;
next[num]=link[x];
w[num]=INF;
link[x]=num;
}
bool count(int y){//判断表中是否有对应值
int x=y%Hash;
for(int j=link[x];j;j=next[j])
if(y==son[j])
return true;
return false;
}
int &operator [](int y){//获取键的对应值
int x=y%Hash;
for(int j=link[x];j;j=next[j])
if(y==son[j])
return w[j];
add(x,y);
Stack[++top]=x;
return w[num];
}
}hashMap;
int GCD(int a,int b){
if(b==0)
return a;
return GCD(b,a%b);
}
int extendedGCD(int x,int y,int &a,int &b){
if(y==0){
a=1;
b=0;
return x;
}
int temp;
int gcd=extendedGCD(y,x%y,a,b);
temp=a;
a=b;
b=temp-x/y*b;
return gcd;
}
int extendBSGS(int A,int B,int C){
//三种特判
if(C==1){
if(!B)
return A!=1;
return -1;
}
if(B==1){
if(A)
return 0;
return -1;
}
if(A%C==0){
if(!B)
return 1;
return -1;
}
int gcd=GCD(A,C);
int D=1,num=0;
while(gcd!=1){//把A,C变成(A,C)=1为止
if(B%gcd)
return -1;
B/=gcd;//从B中约去因子
C/=gcd;//约C中约去因子
D=((LL)D*A/gcd)%C;//将多出的乘给D
num++;//统计约去次数
gcd=GCD(A,C);
}
int now=1;
for(int i=0;i<=num-1;i++){//枚举0~num-1
if(now==B)
return i;
now=((LL)now*A)%C;
}
hashMap.clear();
int Size=ceil(sqrt(C)),Base=1;
for(int i=0;i<=Size-1;i++){//将A^j存进哈希表
hashMap[Base]=min(hashMap[Base],i);//存储答案最小的
Base=((LL)Base*A)%C;
}
for(int i=0;i<=Size-1;i++){//扩展欧几里得求A^j
int x,y;
int gcd=extendedGCD(D,C,x,y);//求出x、y
x=((LL)x*B%C+C)%C;
if(hashMap.count(x))
return i*Size+hashMap[x]+num;//加回num
D=((LL)D*Base)%C;
}
return -1;//无解
}
int main(){
int A,B,C;
while(scanf("%d%d%d",&A,&B,&C)!=EOF&&(A+B+C)){
int res=extendBSGS(A,B,C);
if(res==-1)
printf("No Solution\n");
else
printf("%d\n",res);
}
return 0;
}