大小步算法 BSGS
前提:
当你遇到一个形如
\[a^x\equiv b\pmod p,\ \ p 为素数且 \gcd(a,p)=1,求解 x 的最小整数解
\]
的问题时,你可能会苦恼。
但是对于条件 \(p\) 为素数且 \(\gcd(a,p)=1\),你可能会想到 费马小定理 的 \(a^{p-1}\equiv1\pmod p\)。
所以可以把 \(x\) 分解为 \(k\times (p-1)+t\),这样就可以得出 \(k\) 个 \(a^{p-1}\),然后可以在 \([0,p-1]\) 中枚举 \(t\) 来求解。
但此时的时间复杂度为 \(\text{O}(p)\),通常一个 \(p\) 是一个极大的数,所以并不是很优秀的做法。
大小步算法。
大小步算法(Boby-Step-Giant-Step)也被称为拔山盖世算法、北上广深算法...
如果有人对 分块 或者 筛法 有了解的话,将一个数开方是优秀的复杂度,
那么大小步算法结合这个特点,将 \(x\) 分为了 \(i\times\left\lceil\sqrt p\right\rceil - j\) 的形式。
- 设 \(m = \left\lceil\sqrt p\right\rceil\),原来的方程就变为了
\[a^{i\times m-j}\equiv b\pmod p
\]
- 由此我们在方程的两边分别乘上 \(a^j\),得到
\[(a^{m})^i\equiv b\times a^j \pmod p
\]
-
既然要找出两个相等的数,何必不用 \(hash\) 表来快速查询呢
- 先从 \(0\) 到 \(m\) 枚举 \(j\),将 \(b\times a^j\) 存入 \(hash\) 表中。(Boby-Step)
- 再从 \(1\) 到 \(m\) 枚举 \(i\),将 \((a^m)^i\) 在 \(hash\) 表中查询。(Giant-Step)
-
因为从小到大枚举,所以找到的第一个 \(j\),就是满足 \(x\) 为最小整数解。
代码实现
unordered_map<int,int>vis;
void BSGS()
{
cin >> p >> a >> b;
a %= p; b %= p;
m = ceil(sqrt(p));
am = Pow(a,m,p);
cur = 1;
for (int j = 0; j <= m; ++ j)
{
vis[b*cur%p] = j;
cur = (cur * a) %p;
}
cur = 1;
for (int i = 1; i <= m; ++ i)
{
cur = (cur * am) %p;
if (vis.find(cur) != vis.end())
{
cout << i*m-vis[cur];
return;
}
}
cout << "no solution";
}
再来一遍!
我们的答案皆出自于 \(a^0,a^1,a^2,\dots,a^{p-2}\) 这 \(p-1\) 个数中。
那么将其分为若干组,每个组的大小均设为 \(s\)。那么相邻的每个组的对应的数之间就会相差 \(\times a^s\) 。
- 假如,答案 \(x\) 在第 \(2\) 组中出现了,那么在第 \(1\) 组中就会有 \(b\times a^{-s}\) 出现。
那么就可以只处理出一组的答案,然后再对所有的 \(b\times a^{-ks}\) 查询(二分、哈希等)即可。