注:本篇均为博主个人的理解,如有错误,敬请斧正。
BSGS是用来解决这样一个问题: a x = b ( m o d c ) a^x=b(mod\ c) ax=b(mod c),求最小的非负整数x。
BSGS是一种O( c \sqrt{c} c)的算法。它有解的条件是模数 c c c为质数并且底数 a a a与模数 c c c互质。
它的过程如下:
令
x
=
i
∗
m
−
j
x=i*m-j
x=i∗m−j,
−
j
-j
−j而不是
+
j
+j
+j可以避免用扩欧,如果是
+
j
+j
+j的话要用到扩欧。其中
m
=
⌈
c
⌉
m=\lceil\sqrt{c}\rceil
m=⌈c⌉,则有
a
i
∗
m
−
j
=
b
(
m
o
d
c
)
a^{i*m-j}=b(mod\ c)
ai∗m−j=b(mod c)。
−
j
-j
−j的用处就在这里,如果是
+
j
+j
+j,那么我们要把
a
i
∗
m
a^{i*m}
ai∗m看作一个整体
D
D
D,那么有
D
∗
a
j
=
b
(
m
o
d
c
)
D*a^j=b(mod\ c)
D∗aj=b(mod c),可以用扩展欧几里得求出
a
j
a^j
aj。但是-j的话我们可以把上面的式子移项,可以得到:
(
a
m
)
i
=
b
∗
a
j
(
m
o
d
c
)
(a^m)^i=b*a^j(mod\ c)
(am)i=b∗aj(mod c)。其中
a
a
a,
b
b
b,
c
c
c都是已知的,
m
m
m可以由
c
c
c算出来,那么只有式子中
i
i
i和
j
j
j是未知的,那么我们考虑枚举
i
i
i和
j
j
j。
那么我们会面临这样的问题:为什么将m取为
⌈
c
⌉
\lceil\sqrt{c}\rceil
⌈c⌉?i和j要枚举到什么时候结束?
我们考虑模意义下会出现循环节,所以不需要无线枚举下去,那么多长一定会出现循环节呢?(我觉得有点类似pollard_rho的那个rho)我们发现BSGS有解的条件与费马小定理的适用条件一样,这是因为找循环节时用到了费马小定理来证明。
我们考虑证明 a x m o d ( c − 1 ) = a x ( m o d c ) a ^ {x \ mod (c-1)}=a^x(mod\ c) ax mod(c−1)=ax(mod c)
a x − n ∗ ( c − 1 ) = a x ( m o d c ) a^{x-n*(c-1)}=a^x(mod\ c) ax−n∗(c−1)=ax(mod c)
a
x
a
n
∗
(
c
−
1
)
=
a
x
(
m
o
d
c
)
\frac{a^x}{a^{n*(c-1)}}=a^x(mod\ c)
an∗(c−1)ax=ax(mod c)
a
x
[
a
(
c
−
1
)
]
n
=
a
x
(
m
o
d
c
)
\frac{a^x}{[a^{(c-1)}]^n}=a^x(mod\ c)
[a(c−1)]nax=ax(mod c)
由费马小定理可知:当
c
c
c为质数并且
a
a
a与
c
c
c互质时
a
c
−
1
=
1
(
m
o
d
c
)
a^{c-1}=1(mod\ c)
ac−1=1(mod c)所以
[
a
(
c
−
1
)
]
n
=
1
(
m
o
d
c
)
[a^{(c-1)}]^n=1(mod\ c)
[a(c−1)]n=1(mod c)
于是就有了
a
x
=
a
x
(
m
o
d
c
)
{a^x}={a^x}(mod\ c)
ax=ax(mod c)证明完毕。
那么我们可以得知
x
x
x只需枚举到
c
c
c即可,由于
m
=
⌈
c
⌉
m=\lceil\sqrt{c}\rceil
m=⌈c⌉,所以i和j最大也是
⌈
c
⌉
\lceil\sqrt{c}\rceil
⌈c⌉。
那么,我们开始从
0
0
0到
m
m
m枚举
j
j
j,用哈希表记录每一个
b
∗
a
j
(
m
o
d
c
)
b*a^j(mod\ c)
b∗aj(mod c),这里允许后面的
j
j
j覆盖前面的
j
j
j,因为
x
=
i
∗
m
−
j
x=i*m-j
x=i∗m−j,所以
j
j
j越大,
x
x
x越小,而我们又要求最小的
x
x
x,所以要这么操作。
接下来我们开始从
1
1
1到
m
m
m枚举
i
i
i,计算
(
a
m
)
i
(
m
o
d
c
)
(a^m)^i(mod\ c)
(am)i(mod c),在哈希表中查找,如果与表中的某个哈希值相同,那么我就计算出现在的
x
=
i
∗
m
−
j
x=i*m-j
x=i∗m−j,就是答案。因为
i
i
i每增大
1
1
1,
x
x
x就会增大
m
m
m,而
j
j
j最大就是
m
m
m,所以只要
i
i
i最小就好。而因为我们减了
j
j
j,所以要从
1
1
1开始枚举
i
i
i,否则会出现负数。
接下来是代码(poj2417) `
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
using namespace std;
long long a,b,c;//计算a^x=b(mod c)的最小x
map<long long,long long> mp;
long long gcd(long long x,long long y)
{
return y?gcd(y,x%y):x;
}
long long ksm(long long x,long long y,long long mod)
{
long long res=1;
while(y)
{
if(y&1)
res=(res*x)%mod;
x=(x*x)%mod;
y>>=1;
}
return res;
}
int main()
{
while(~scanf("%lld%lld%lld",&c,&a,&b))
{
mp.clear();
if(gcd(a,c)!=1)//如果a与c不互质,一定无解
{
printf("no solution\n");
continue;
}
long long m=(long long)ceil(sqrt(c)),ans,pd=0;//pd记录是否有解
for(int j=0;j<=m;++j)//枚举b*a^j存入哈希表(map)
{
if(j==0)
{
ans=b%c;
mp[ans]=j;
continue;
}
ans=(ans*a)%c;
mp[ans]=j;//用后来的覆盖前面的,因为j越大,-j越小
}
long long x=ksm(a,m,c);//先计算a^m,因为之后要计算(a^m)^i
ans=1;
for(int i=1;i<=m;++i)
{
ans=(ans*x)%c;
if(mp[ans])//i越小,求出的x越小(j<=m,但是i每+1,x就要+m)
{
x=i*m-mp[ans];
printf("%lld\n",(x%c+c)%c);
pd=1;
break;
}
}
if(!pd)
printf("no solution\n");
}
return 0;
}
PS:第一次用markdown,感觉一开始用真的好难受,但是看上去的效果不错。多亏Mys_C_K教我,不然可能一天都发不出博客,在此感谢Mys_C_K。
题单:
poj2417 Discrete Logging
洛谷3846可爱的质数
洛谷2485计算器
洛谷3306SDOI随机数生成器(我有写题解)