问题
A
x
≡
B
(
m
o
d
C
)
A^x \equiv B (\bmod C)
Ax≡B(modC),求最小的x或输出无解。
暴力
先给出一个定理:
A
n
≡
A
n
m
o
d
φ
(
C
)
≡
B
(
m
o
d
C
)
A^n \equiv A^{n \bmod \varphi (C)} \equiv B (\bmod C)
An≡Anmodφ(C)≡B(modC)
有了这个定理,我们可以在
[
0
,
φ
(
C
)
]
[0,\varphi (C)]
[0,φ(C)]中枚举
x
x
x,如果这段区间都无解,那么这个方程无整数解。
BSGS算法
前提:
C
C
C为质数。
BSGS算法是一种用分块优化的暴力。设
m
=
⌈
C
⌉
m=\left \lceil \sqrt C \right \rceil
m=⌈C⌉,
再设
x
=
i
∗
m
−
j
x=i*m-j
x=i∗m−j,所以
A
x
=
A
i
∗
m
/
A
j
A^x = A^{i*m}/A^j
Ax=Ai∗m/Aj,
上式再化成
A
i
∗
m
/
A
j
≡
B
(
m
o
d
C
)
A^{i*m}/A^j \equiv B (\bmod C)
Ai∗m/Aj≡B(modC)
A
i
∗
m
≡
B
∗
A
j
(
m
o
d
C
)
A^{i*m} \equiv B* A^j(\bmod C)
Ai∗m≡B∗Aj(modC)
这样以后,我们枚举
i
i
i,然后
A
j
A^j
Aj就可以确定一个值
B
/
A
i
∗
m
B/A^{i*m}
B/Ai∗m。这样还是麻烦了一点,因为与
i
i
i套上了关系,不方便预处理。不妨把
B
∗
A
j
B*A^j
B∗Aj连起来看,如果存在一个
j
j
j满足
B
∗
A
j
=
A
i
∗
m
B*A^j = A^{i*m}
B∗Aj=Ai∗m,那么就做了一大半了。因为
x
=
i
∗
m
−
j
x=i*m-j
x=i∗m−j,把
i
i
i和
j
j
j带进去就可以求到
x
x
x了。
至于如何快速得到
A
i
∗
m
A^{i*m}
Ai∗m是否为某个
j
j
j的
B
∗
A
j
B*A^j
B∗Aj,这个问题可以提前预处理出来,用一个hash表存贮
h
a
s
h
[
B
∗
A
j
]
=
j
hash[B*A^j]=j
hash[B∗Aj]=j,这个
j
j
j应该从
0
0
0开始枚举到
m
m
m。如果
h
s
a
h
[
A
i
∗
m
]
hsah[A^{i*m}]
hsah[Ai∗m]不为
0
0
0,则
j
=
h
a
s
h
[
A
i
∗
m
]
j=hash[A^{i*m}]
j=hash[Ai∗m]即为
j
j
j的解。
exBSGS算法
前提:无
既然没有限制,那就可能不是互质的关系。设
d
=
gcd
(
A
,
C
)
d=\gcd(A,C)
d=gcd(A,C),再设
A
=
a
∗
d
A=a*d
A=a∗d,
C
=
c
∗
d
C=c*d
C=c∗d这是一点问题都没有的。如果
B
B
B不是
d
d
d的倍数,除非
B
B
B是
1
1
1(此时
x
=
0
x=0
x=0)否则无解。所以
B
B
B也可以表示成
B
=
b
∗
d
B=b*d
B=b∗d。
所以上式被表示成
(
a
∗
d
)
x
≡
b
∗
d
(
m
o
d
c
∗
d
)
(a*d)^x \equiv b*d(\bmod c*d)
(a∗d)x≡b∗d(modc∗d)
去掉一个
d
d
d,得
a
∗
(
a
∗
d
)
x
−
1
≡
b
(
m
o
d
c
)
a*(a*d)^{x-1} \equiv b(\bmod c)
a∗(a∗d)x−1≡b(modc)
也可以写成
(
A
1
/
d
)
∗
A
x
−
1
≡
b
(
m
o
d
c
)
(A^1/d)*A^{x-1} \equiv b(\bmod c)
(A1/d)∗Ax−1≡b(modc)
继续做gcd,每次gcd都会提出一个
(
A
/
d
)
(A/d)
(A/d)这样的东西出来。记所有的
d
d
d之积为
q
q
q,即
q
=
∏
d
i
q=\prod d_i
q=∏di,做了
n
u
m
num
num次后会变成
(
A
n
u
m
/
q
)
∗
A
x
−
n
u
m
≡
B
/
q
(
m
o
d
C
/
q
)
(A^{num}/q)*A^{x-num}\equiv B/q(\bmod C/q)
(Anum/q)∗Ax−num≡B/q(modC/q)
一直做到
gcd
(
A
,
C
/
q
)
=
1
\gcd(A,C/q)=1
gcd(A,C/q)=1之后,我们称
A
A
A和
C
/
q
C/q
C/q互质了,这就满足了BSGS的条件。
其中
A
n
u
m
/
q
A^{num}/q
Anum/q可以在gcd时统计,为已知,我们用
D
D
D来表示它,即
D
=
A
n
u
m
/
q
D=A^{num}/q
D=Anum/q。设
y
=
x
−
n
u
m
y=x-num
y=x−num,
B
′
=
B
/
q
B'=B/q
B′=B/q,
C
′
=
C
/
q
C'=C/q
C′=C/q。因为
q
q
q也是已知的,所以
B
′
B'
B′和
C
′
C'
C′都是已知的。式子变成了
D
∗
A
y
≡
B
′
(
m
o
d
C
′
)
D*A^y\equiv B'(\bmod C')
D∗Ay≡B′(modC′)
接下来用BSGS的思路来考虑,设
y
=
i
∗
m
−
j
y=i*m-j
y=i∗m−j,那么有
D
∗
A
i
∗
m
/
A
j
≡
B
′
(
m
o
d
C
′
)
D*A^{i*m}/A^j\equiv B'(\bmod C')
D∗Ai∗m/Aj≡B′(modC′)
移项有
D
∗
A
i
∗
m
≡
B
′
∗
A
j
(
m
o
d
C
′
)
D*A^{i*m}\equiv B'*A^j(\bmod C')
D∗Ai∗m≡B′∗Aj(modC′)
为了解决这个式子,在一开始
i
=
0
i=0
i=0时,左边就为
D
D
D。最后用BSGS我们求到的是
i
i
i和
j
j
j,这样用
y
=
i
∗
m
−
j
y=i*m-j
y=i∗m−j可以求到
y
y
y,进而根据
y
=
x
−
n
u
m
⇒
x
=
y
+
n
u
m
y=x-num\Rightarrow x=y+num
y=x−num⇒x=y+num可以求到
x
x
x,
x
=
i
∗
m
−
j
+
n
u
m
x=i*m-j+num
x=i∗m−j+num。
再补讲一个细节吧,我们的
D
D
D是随gcd实时更新的,也就是
D
∗
A
x
−
n
u
m
≡
B
/
q
(
m
o
d
C
/
q
)
D*A^{x-num}\equiv B/q(\bmod C/q)
D∗Ax−num≡B/q(modC/q)
如果某一时刻出现了
D
=
B
/
q
D=B/q
D=B/q的情况,也就是说式子变成了
D
∗
A
x
−
n
u
m
≡
D
(
m
o
d
C
/
q
)
D*A^{x-num}\equiv D(\bmod C/q)
D∗Ax−num≡D(modC/q)
那么此时的
A
x
−
n
u
m
A^{x-num}
Ax−num必须为1,即
x
−
n
u
m
=
0
x-num=0
x−num=0,所以
x
=
n
u
m
x=num
x=num,同时可以结束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;
}