BSGS 是一个用来求高次同余方程的暴力算法。
先看一道例题:
给定 ,保证 且 ,求 的最小自然数解 。
显然,我们可以暴力从小到大枚举 ,判断每个 是否合法。根据欧拉的神奇定理,,我们只需要枚举到 即可。时间复杂度 。
这样显然不够好,很多时候 都十分巨大。考虑优化,我们可以把 拆开,拆成 ,其中 。那么有:
这样我们就可以用一个哈希表存每个 对应的最大的 ,然后枚举 ,快速找到 对应的 ,答案即为 。
显然,此时 取 是最优的。此时时间复杂度为 。如果用 map 来哈希的话时间复杂度会多只 。
代码如下:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <map>
using namespace std;
inline int BSGS(int a,int b,int p)
{
map<int,int> mp;
int val=1,t=sqrt(p)+1;
for(int B=1;B<=t;B++)
{
val=1ll*val*a%p;
mp[1ll*b*val%p]=B;
}
int cur=val;
for(int A=1;A<=t;A++)
{
if(mp.find(val)!=mp.end())
{
return A*t-mp[val];
}
val=1ll*val*cur%p;
}
return -1;
}
int main()
{
int p,a,b;
scanf("%d%d%d",&p,&a,&b);
int x=BSGS(a,b,p);
if(x==-1)
{
puts("no solution");
return 0;
}
printf("%d\n",x);
return 0;
}
但是有些时候,,用不了 BSGS。这时候我们就需要 exBSGS 了。
例如这道题:P4195 【模板】扩展 BSGS/exBSGS
当 时,我们令 ,那么有:
如果 仍然不为 ,那么继续做下去,直到 为止( 为所有 的乘积)。
这时我们的方程变成了这样( 是操作的次数):
就可以用 BSGS 计算答案了。
注意特判 和 的特殊情况。
代码如下:
#include <iostream>
#include <cstdio>
#include <map>
#include <cmath>
using namespace std;
inline int gcd(int a,int b)
{
int t=a%b;
while(t>0)
{
a=b;
b=t;
t=a%b;
}
return b;
}
inline int exBSGS(int a,int b,int p)
{
a%=p;
b%=p;
if(b==1||p==1)
{
return 0;
}
int cnt=0,val=1;
while(1)
{
int d=gcd(a,p);
if(d==1)
{
break;
}
if(b%d!=0)
{
return -1;
}
cnt++;
p/=d;
b/=d;
val=1ll*val*(a/d)%p;
if(val==b)
{
return cnt;
}
}
map<int,int> mp;
int val2=1,t=sqrt(p)+1;
for(int B=1;B<=t;B++)
{
val2=1ll*val2*a%p;
mp[1ll*b*val2%p]=B;
}
int cur=1ll*val2*val%p;
for(int A=1;A<=t;A++)
{
if(mp.find(cur)!=mp.end())
{
return A*t-mp[cur]+cnt;
}
cur=1ll*cur*val2%p;
}
return -1;
}
int main()
{
int a,b,p;
while(1)
{
scanf("%d%d%d",&a,&p,&b);
if(a==0&&b==0&&p==0)
{
break;
}
int res=exBSGS(a,b,p);
if(res<0)
{
puts("No Solution");
}
else
{
printf("%d\n",res);
}
}
return 0;
}