大步小步算法(baby step giant step,BSGS),是一种用来求解离散对数(即模意义下对数)的算法,即给出 a x ≡ b ( m o d m ) a^{x} \equiv b\pmod{m} ax≡b(modm)中 a , b , m a,b,m a,b,m的值(这里保证a和m互质),求解x。
实际上,大步小步算法就是对暴力枚举的一个简单的改进,使用了类似meet in the middle的思想。我们把 x x x拆成 A t − B At-B At−B,拆分成这种形式是为了后面移项后指数上能是正的,然后原式就可以化为 a A t − B ≡ b ( m o d m ) a^{At-B } \equiv b \pmod{m} aAt−B≡b(modm),即 a A t ≡ b a B ( m o d m ) a^{At} \equiv ba^{B} \pmod{m} aAt≡baB(modm)。对于这个式子,如果我们固定一个 t t t值的话,随着 B B B的取值不同 A A A的取值也会跟着变化(反过来说也一样)。然后我们预计算出右侧所有可能的取值并存下来,接着计算出左边可能的值,当发现某个值已经在右边出现过,这时的 A t − B At-B At−B 就是我们要求的 x x x 。
B可能的取值有 m m o d t m \mod t mmodt个,A可能的取值有 ⌊ m / t ⌋ \left \lfloor m/t \right \rfloor ⌊m/t⌋个,不难看出,取 t = ⌈ m ⌉ t=\left \lceil \sqrt{m} \right \rceil t=⌈m⌉是最好的。此时取 A , B ∈ [ 1 , t ] A,B\in [1,t] A,B∈[1,t]可以保证把 x ∈ [ 1 , m − 1 ] x\in [1,m-1] x∈[1,m−1]全部枚举一遍。时间复杂度为 O ( m ) O(\sqrt{m} ) O(m)。
至于如何判定右边的数有没有出现过,用哈希表是最简单的(STL中的unordered_map
是用哈希表实现的,一般可以直接使用),如果用map
,复杂度会多一个log 。下面给出洛谷上的模板题和实现代码。
P3846 [TJOI2007] 可爱的质数/【模板】BSGS
ll BSGS(ll a, ll b, ll m)
{
unordered_map<ll, ll> hs;
ll cur = 1, t = sqrt(m) + 1;
for (ll B = 1; B <= t; B++)
{
cur = cur * a % m;
hs[b * cur % m] = B;
}
ll now = cur; // 此时cur为a^t
for (int A = 1; A <= t; A++)
{
auto it = hs.find(now);
if (it != hs.end())
return A * t - it->second;
now = now * cur % m;
}
return -1;
}
上面的算法只针对 a a a和 m m m互质的情形,如果不互质,又该如何计算?我们尝试把它转化成 a a a和 m m m互质的情形。
若 a a a和 m m m不互质,则存在他们的最大公因数 d = g c d ( a , m ) d=gcd(a,m) d=gcd(a,m)。将最大公因数除去即可转化成互质的情况,即 a x ≡ b ( m o d m ) a^{x} \equiv b \pmod{m} ax≡b(modm)都除以 d d d,得到 a d a x − 1 ≡ b d ( m o d m d ) \frac{a}{d} a^{x-1} \equiv \frac{b}{d} \pmod{\frac{m}{d}} daax−1≡db(moddm),将 a d \frac{a}{d} da记作系数 k k k。若 d d d不能整除 b b b,则无解;反之,就能直接用大步小步法求解了(左侧多了一个系数,但这显然影响不大,把大步小步算法稍作修改即可)。需要注意的是,此时用大步小步法求出来的是 x − 1 x-1 x−1,最后结果还需要加一才行。
参考代码如下:
ll exBSGS(ll a, ll b, ll m)
{
a %= m;
b %= m;
if (b == 1 || m == 1)
return 0;
ll d = gcd(a, m);
if (b % d != 0)
return -1;
ll k = a / d;
b /= d;
m /= d;
unordered_map<ll, ll> hs;
ll cur = 1, t = sqrt(m) + 1;
for (ll B = 1; B <= t; B++)
{
cur = cur * a % m;
hs[b * cur % m] = B;
}
ll now = k * cur % m; // 此时cur为a^t,注意乘上系数
for (int A = 1; A <= t; A++)
{
auto it = hs.find(now);
if (it != hs.end())
return A * t - it->second + 1; // 别忘了加一
now = now * cur % m;
}
return -1;
}