线性同余方程 b x ≡ 1 ( m o d p ) bx\equiv1(mod \,p) bx≡1(modp),可以用exgcd或者费马小定理解出。但对于高次同余方程 a x ≡ b ( m o d p ) a^x\equiv b(mod \,p) ax≡b(modp),需要用BSGS或者扩展BSGS解出。
BSGS
问题描述:对于整数a,b,p,其中a,p互质,求一个最小的非负整数x,满足 a x ≡ b ( m o d p ) a^x\equiv b(mod \,p) ax≡b(modp)。
BSGS是Baby Step,Giant Step的缩写,又叫大小步算法。因为a,p互质,根据欧拉定理有 a φ ( p ) ≡ 1 ( m o d p ) a^{\varphi(p)}\equiv 1 (mod \, p) aφ(p)≡1(modp)成立,也就是说当x>= φ ( p ) \varphi(p) φ(p)时,会出现循环节,在模p的意义下, a x a^x ax和 a x + φ ( p ) a^{x+\varphi(p)} ax+φ(p)意义是一样的。因此最小非负整数解如果存在,一定在 [ 0 , φ ( p ) − 1 ] [0, \varphi(p)-1] [0,φ(p)−1]之间。
如果p值不大,完全可以暴力枚举求解。p值若比较大,如接近 2 32 2^{32} 232,要用到BSGS算法。
设 x = i ∗ t − j x=i * t - j x=i∗t−j,其中 t = ⌈ p ⌉ t = \lceil \sqrt p \rceil t=⌈p⌉,0 ≤ \,\leq ≤ j j j ≤ \,\leq ≤ t − 1 t-1 t−1,0 ≤ \,\leq ≤ i i i ≤ \,\leq ≤ t t t。t取定值,通过 i i i和 j j j的搭配, x x x就可以取遍 [ 0 , φ ( p ) − 1 ] [0, \varphi(p)-1] [0,φ(p)−1]之间所有的数。带入:
a
i
∗
t
−
j
≡
b
(
m
o
d
p
)
(
a
t
)
i
≡
b
∗
a
j
(
m
o
d
p
)
\begin{aligned} a^{i*t-j}&\equiv b (mod \, p) \\ (a^t)^{i}&\equiv b*a^j (mod \, p) \end{aligned}
ai∗t−j(at)i≡b(modp)≡b∗aj(modp)
对于所有的0
≤
\,\leq
≤
j
j
j
≤
\,\leq
≤
t
−
1
t-1
t−1,把
b
∗
a
j
m
o
d
p
b*a^j mod \, p
b∗ajmodp的结果插入一个hash表。可以用map或者拉链法实现hash表。
从小到大枚举i的可能值,即0 ≤ \,\leq ≤ i i i ≤ \,\leq ≤ t t t,计算 ( a t ) i m o d p (a^t)^{i} \mod\,p (at)imodp,在hash表中查找是否存在对应的 j j j,找到的第一个 i i i和 j j j的组合就是答案 i ∗ t − j i*t-j i∗t−j。时间复杂度 O ( p ) O(\sqrt p) O(p)。用map实现hash表时间复杂度还要带一个查找时的log。
怎么保证求出的是最小非负整数解?把 b ∗ a j m o d p b*a^j mod \, p b∗ajmodp的结果插入hash表时, j j j值是从小到大的,如果前后两个的 v a l val val值相同,则保存的是较大的 j j j。枚举 i i i是也是从小到大。对于表达式 i ∗ t − j i*t-j i∗t−j, i i i取最小值, j j j取最大值,结果就是最小的。
核心代码:
int BSGS(int a, int b, int p)
{
// if (a == 0 && b == 1) return 0; // 0^0定义不存在,这种情况不需要特判
if (a == 0 && b == 0) return 1;
if (a == 0 && b != 0) return -1;
q.clear();
int t = ceil(sqrt(p));
b %= p;
for (int j = 0; j < t; ++j)
{
int val = (long long)b * szpow(a, j, p) % p; // 两个int相乘可能会溢出
q[val] = j;
}
a = szpow(a, t, p);
for (int i = 0; i <= t; ++i)
{
int val = szpow(a, i, p);
if (q.find(val) != q.end() && i * t - q[val] >= 0) return i * t - q[val];
}
return -1;
}
注意事项:在用快速幂求解 a b m o d p a^b mod \,p abmodp时,也要开long long,否则两个int相乘可能会溢出;map实现hash表,key值可以取很大,也可以取负值,这点很方便。
int szpow(int a, int b, int p)
{
long long r = 1, base = a; // 不开long long会溢出
while (b)
{
if (b & 1) r = r * base % p;
base = base * base % p;
b >>= 1;
}
return r % p;
}
拉链法实现的hash表:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <algorithm>
using namespace std;
const int PP = 2887777;
int p, a, b;
int szfirst[PP+10], sznext[70000], cnt;
struct node
{
int val, j;
}szdate[70000];
inline void szadd(int val, int j)
{
int u = val % PP;
++cnt;
sznext[cnt] = szfirst[u];
szfirst[u] = cnt;
szdate[cnt].val = val, szdate[cnt].j = j;
}
int szfind(int val)
{
int u = val % PP;
int d = szfirst[u];
while (d)
{
if (szdate[d].val == val) return szdate[d].j;
d = sznext[d];
}
return -1;
}
int szpow(int a, int b, int p)
{
long long r = 1, base = a; // 不开long long会溢出
while (b)
{
if (b & 1) r = r * base % p;
base = base * base % p;
b >>= 1;
}
return r % p;
}
int BSGS(int a, int b, int p)
{
if (a == 0 && b == 1) return 0;
if (a == 0 && b == 0) return 1;
if (a == 0 && b > 1) return -1;
int t = ceil(sqrt(p)); // 2^16
b %= p;
for (int j = 0; j < t; ++j)
{
int val = (long long)b * szpow(a, j, p) % p; // 两个int相乘可能会溢出
szadd(val, j);
}
a = szpow(a, t, p);
for (int i = 0; i <= t; ++i)
{
int val = szpow(a, i, p);
int j = szfind(val);
if (j != -1 && i * t - j >= 0) return i * t - j;
}
return -1;
}
int main()
{
scanf("%d%d%d", &p, &a, &b);
int ans;
ans = BSGS(a, b, p);
if (ans == -1) printf("no solution\n");
else printf("%d\n", ans);
return 0;
}
题目:
1.洛谷:P3846 【模板】BSGS
2.洛谷:P2485 计算器
扩展BSGS
当a,p不一定互质时,需要用到扩展BSGS。总体思路:将a,p约分,使其互质,然后用BSGS求解。
设
d
=
g
c
d
(
a
,
p
)
d=gcd(a,p)
d=gcd(a,p),根据同余性质,下式成立:
a
x
d
≡
b
d
(
m
o
d
p
d
)
\frac{a^x}{d}\equiv \frac{b}{d} (mod \,\frac{p}{d})
dax≡db(moddp)
此时如果
d
∤
b
d \nmid b
d∤b,要么此时
b
=
1
b=1
b=1,
x
=
0
x=0
x=0,否则根据裴蜀定理可知,上式无解。因此,在最开始的时候判断
b
b
b是否为1,以后凡是遇到
d
∤
b
d \nmid b
d∤b的情况,说明无解。
a
d
a
x
−
1
≡
b
d
(
m
o
d
p
d
)
t
=
a
d
b
=
b
d
p
=
p
d
t
a
x
−
1
≡
b
(
m
o
d
p
)
\begin{aligned} \frac{a}{d}\,{a^{x-1}} &\equiv \frac{b}{d} (mod \,\frac{p}{d}) \\ t = \frac{a}{d}\quad b &=\frac{b}{d} \quad p = \frac{p}{d} \\ t \,{a^{x-1}} &\equiv b (mod \,p) \\ \end{aligned}
daax−1t=dabtax−1≡db(moddp)=dbp=dp≡b(modp)
继续判断此时a,p是否互质,如果不互质,按照上面的方法继续约分。直到互质。得到同余方程:
a
k
∏
i
=
1
k
d
i
a
x
−
k
≡
b
∏
i
=
1
k
d
i
(
m
o
d
p
∏
i
=
1
k
d
i
)
\frac{a^k}{\prod _{i=1}^{k}d_i}\, a^{x-k} \equiv \frac{b}{\prod _{i=1}^{k}d_i}\, (mod \, \frac{p}{\prod _{i=1}^{k}d_i})
∏i=1kdiakax−k≡∏i=1kdib(mod∏i=1kdip)
化减为:
t
a
x
1
≡
b
1
(
m
o
d
p
1
)
t\,a^{x1}\equiv b1(mod \, p1)
tax1≡b1(modp1)
按照普通的BSGS解即可。最后求出的答案再加上k。此时求出的答案是
≥
k
\ge k
≥k的,还需要特判
[
1
,
k
]
[1,k]
[1,k]的解的情况。
while (1)
{
scanf("%d%d%d", &a, &p, &b);
if (!a && !p && !b) break;
if (b == 1) // 特判b为1的情况
{
printf("0\n"); continue;
}
int ans = 0, k = 0, d, t = 1;
a %= p;
b %= p;
while (1)
{
d = gcd(a, p);
if (d == 1) break; // 如果a、p互质,按照普通BSGS求解
++k;
if (b % d) // 如果d不能整除b,根据裴蜀定理,无解
{
printf("No Solution\n");
ans = -1;
break;
}
t = (long long) t * (a / d) % p;
// t = a / d * t; // 这样写有可能溢出,只能过40%的数据
b /= d; p /= d;
}
// 首先特判[1, k]是否满足
if (ans != -1)
for (int i = 1; i <= k; ++i)
{
if (szpow(a, i, p) == b % p)
{
printf("%d\n", i);
ans = -1;
break;
}
}
if (ans == -1) continue;
ans = BSGS(a, b, p, t);
if (ans == -1) printf("No Solution\n");
else printf("%d\n", ans + k); // 最终结果还要加k
}
注意事项:在求解BSGS的时候,不要忘记乘上t。
题目:
1.洛谷:P4195 【模板】扩展BSGS