问题
,求最小的x或输出无解。
暴力
先给出一个定理:
有了这个定理,我们可以在
中枚举
,如果这段区间都无解,那么这个方程无整数解。
BSGS算法
前提:
为质数。
BSGS算法是一种用分块优化的暴力。设
,
再设
,所以
,
上式再化成
这样以后,我们枚举
,然后
就可以确定一个值
。这样还是麻烦了一点,因为与
套上了关系,不方便预处理。不妨把
连起来看,如果存在一个
满足
,那么就做了一大半了。因为
,把
和
带进去就可以求到
了。
至于如何快速得到
是否为某个
的
,这个问题可以提前预处理出来,用一个hash表存贮
,这个
应该从
开始枚举到
。如果
不为
,则
即为
的解。
exBSGS算法
前提:无
既然没有限制,那就可能不是互质的关系。设
,再设
,
这是一点问题都没有的。如果
不是
的倍数,除非
是
(此时
)否则无解。所以
也可以表示成
。
所以上式被表示成
去掉一个
,得
也可以写成
继续做gcd,每次gcd都会提出一个
这样的东西出来。记所有的
之积为
,即
,做了
次后会变成
一直做到
之后,我们称
和
互质了,这就满足了BSGS的条件。
其中
可以在gcd时统计,为已知,我们用
来表示它,即
。设
,
,
。因为
也是已知的,所以
和
都是已知的。式子变成了
接下来用BSGS的思路来考虑,设
,那么有
移项有
为了解决这个式子,在一开始
时,左边就为
。最后用BSGS我们求到的是
和
,这样用
可以求到
,进而根据
可以求到
,
。
再补讲一个细节吧,我们的
是随gcd实时更新的,也就是
如果某一时刻出现了
的情况,也就是说式子变成了
那么此时的
必须为1,即
,所以
,同时可以结束exBSGS。
代码
BSGS模版例题 洛谷2485 [SDOI2011]计算器
#include<map>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
map<ll,ll> hash;
ll power(ll a,ll n,ll mod)//a^n%mod
{
ll re=1;a%=mod;
while(n)
{
if(n&1) re=re*a%mod;
a=a*a%mod;
n>>=1;
}
return re;
}
void bsgs(ll y,ll z,ll p)//y^x=z(mod p)
{
if(y==0 && z==0){puts("1");return ;}//几句特判
if(y==0 && z!=0){puts("Orz, I cannot find x!");return;}
hash.clear();
ll m=ceil(sqrt(p));
ll tmp=z%p;hash[tmp]=0;//右边z*A^j,当j=0时为z
for(ll i=1;i<=m;i++)
{
tmp=tmp*y%p;
hash[tmp]=i;
}
ll t=power(y,m,p);
tmp=1;//左边y^(i*m),当i=0时为1
for(ll i=1;i<=m;i++)
{
tmp=tmp*t%p;//i每加1,多乘y^(i*m)
if(hash[tmp])
{
ll ans=i*m-hash[tmp];
printf("%lld\n",(ans%p+p)%p);
return ;
}
}
puts("Orz, I cannot find x!");
}
int main()
{
int T,opt;
ll x,y,p,m,z;
scanf("%d%d",&T,&opt);
while(T--)
{
scanf("%lld%lld%lld",&y,&z,&p);
y%=p;
if(opt==1) printf("%lld\n",power(y,z,p));
else if(opt==2)
{
z%=p;
if(y==0 && z!=0) puts("Orz, I cannot find x!");
else printf("%lld\n",z*power(y,p-2,p)%p);//z/y
}
else bsgs(y,z,p);
}
return 0;
}
exBSGS模板:
#include<map>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
map<ll,ll> hash;
inline ll gcd(ll a,ll b)
{
ll tmp;
while(tmp=a%b) a=b,b=tmp;
return b;
}
inline ll multi(ll x,ll y,ll mod)//快速乘
{
ll tmp=x*y-(ll)(((long double)x*y+0.5)/mod)*mod;
return tmp<0?tmp+mod:tmp;
}
ll power(ll a,int b,ll m)//快速幂 a^b
{
ll re=1;
while(b)
{
if(b&1) re=multi(re,a,m);
a=multi(a,a,m);
b>>=1;
}
return re;
}
int main()
{
ll a,b,m;//a^x=b(mod m)
scanf("%lld%lld%lld",&a,&b,&m);
ll D=1,num=0;
while(1)
{
ll d=gcd(a,m);
if(d==1) break;
if(b%d){puts("INF");return 0;}//b不是d的倍数,无解
b/=d;m/=d;
num++;
D=multi(D,a/d,m);
if(D==b){printf("%lld",num);return 0;}//系数等于余数,此时x-num为0
}
ll now=b;//当j=0时,式子右边=b
hash[now]=0;
int mm=ceil(sqrt(m));//记得向上取整
for(int i=1;i<=mm;i++)
{
now=multi(now,a,m);
hash[now]=i;
}
now=D;//当i=0时,式子左边=D
ll q=power(a,mm,m);//每个i+1,now增大a^mm倍
for(int i=1;i<=mm;i++)
{
now=multi(now,q,m);
if(hash[now])
{
printf("%lld\n",(((ll)i*mm-hash[now]+num)%m+m)%m);
return 0;
}
}
puts("INF");//最后再判一次
return 0;
}