大小步算法 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\) 的形式。

  1. \(m = \left\lceil\sqrt p\right\rceil\),原来的方程就变为了

\[a^{i\times m-j}\equiv b\pmod p \]

  1. 由此我们在方程的两边分别乘上 \(a^j\),得到

\[(a^{m})^i\equiv b\times a^j \pmod p \]

  1. 既然要找出两个相等的数,何必不用 \(hash\) 表来快速查询呢

    • 先从 \(0\)\(m\) 枚举 \(j\),将 \(b\times a^j\) 存入 \(hash\) 表中。(Boby-Step)
    • 再从 \(1\)\(m\) 枚举 \(i\),将 \((a^m)^i\)\(hash\) 表中查询。(Giant-Step)
  2. 因为从小到大枚举,所以找到的第一个 \(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}\) 查询(二分、哈希等)即可。

posted @ 2023-01-10 09:04  Ciaxin  阅读(39)  评论(0编辑  收藏  举报