定义
BSGS算法中文名叫”大步小步算法”,用来求解如下同余方程x的最小正整数解:
//这里p为素数, a、b、p已知,且0<=a,b< p(如果a,b大于p,可将他们对p取模)。
//显然若a为p的一个原根,则x即为b关于a的离散对数(mod p),故此算法可求离散对数。
算法原理
将x重写为
x=i∗m+j
x
=
i
∗
m
+
j
其中
m=⌈p–√⌉,i=x/m,j=x%m.
m
=
⌈
p
⌉
,
i
=
x
/
m
,
j
=
x
%
m
.
显然
0≤i<m,0≤j<m.
0
≤
i
<
m
,
0
≤
j
<
m
.
那么我们就可以分别枚举i和j,以找到符合条件的x。
这里采用的方法是,先枚举j,将其所有的结果放入一个哈希表中,然后再枚举i,看能不能找到一个满足条件的j。
算法复杂度分析
这种做法毋庸置疑,肯定是对的。但我们得思考为什么这样做。
我们可以结合幂的性质来思考,
ax+y=ax∗ay
a
x
+
y
=
a
x
∗
a
y
,幂的和的关系会转换成值的积,而积我们往往只会枚举sqrt(p),所以我们可以使j< sqrt(p),再使前面由
⌈p–√⌉∗i
⌈
p
⌉
∗
i
,从而减少复杂度。所以这样做的关键在于幂的和可以变成两个数的乘积(思想很重要)。
步骤
1.令
m=⌈p–√⌉
m
=
⌈
p
⌉
2.枚举j,j的范围是[0,…,m)。
将
ajmodp
a
j
mod
p
作为键存入hash表,其值为j。
3.枚举i,i的范围是[0,…m)。
设
A=(am)i
A
=
(
a
m
)
i
是已知(代入i),设未知数j,满足
A∗(aj)≡B(modp)
A
∗
(
a
j
)
≡
B
(
mod
p
)
,那么j即为第二步枚举的j,而
aj
a
j
即为第二步放入hash表内的值,由扩展欧几里得可求出
aj
a
j
的值。在hash表里找
aj
a
j
的值,如果找到了则记录j的值,跳出此循环。
4.最后的结果即为
i∗m+j
i
∗
m
+
j
。
实现
/*==================================================*\
| Baby-Step-Giant-Step 大步小步算法
| 求 a^x === b (mod p) 中的 x值 -- 此处p仅为素数
| 实际运用中用自己的hash表代替map可防TLE
\*==================================================*/
LL BSGS(LL a, LL b, LL p) {
a %= p; b %= p;
map<LL, LL> h;
LL m = ceil(sqrt(p)), x, y, d, t = 1, v = 1;
for(LL i = 0; i < m; ++i) {
if(h.count(t)) h[t] = min(h[t], i);
else h[t] = i;
t = (t*a) % p;
}
for(LL i = 0; i < m; ++i) {
d = extgcd(v, p, x, y);
x = (x* b/d % p + p) % (p);
if(h.count(x)) return i*m + h[x];
v = (v*t) % p;
}
return -1;
}