Note:由于网上很多说一套写一套的资料,让我痛苦了整整两天。在结合了一篇优秀的参考资料后,终于研究得差不多了。我觉得不把这篇总结放出来都有点对不起大家,所以特地把这段总结放上来。其实是希望大家能批评指正一下。
大步小步算法
1. 问题
求解关于
2. 暴力方法
3. 大步小步算法(BSGS)
方法
我们可以设
由于
因此,对于所有的
很明显参数
这样预处理的时间复杂度为
4. 扩展大步小步算法(BSGSEx)
①问题
求解关于
②方法
考虑把方程转换成
转换原方程后可以得到这样一个等价的方程(想想扩展欧几里得):
由裴蜀定理,若
当
相当于得到了模方程=
注意,这里
令
可知
由于
③实际操作
我们可以用一些奇技淫巧避免求逆元。
(1)对于 BSGS
我们可以设
可以避免求逆元。 但是从上式推导到下式是要用到逆元的,因此逆元必须存在。
(2)对于 BSGSEx
由于上式的存在,若要递归进行 BSGSEx,我们难以避免求逆元。 所以我们可以将整个过程改成非递归的。这就要求我们记录一些东西。
设原式为
因此我们最后的公式看上去是这样的:(两个
可以变形得:
然后就可以套用大步小步算法了。设
枚举
5. 参考代码
//c++ 11 is needed
INT BSGSEx(INT a, INT b, INT c) //-1 for no solution
{
a %= c;
b %= c;
if (b == 1)
return 0;
INT count_ = 0; //rec_1
INT base = 1; //rec_2
for (INT g = gcd(a, c); g != 1; g = gcd(a, c))
{
if (b % g) return -1; //exit_1
b /= g;
c /= g;
base = base * (a / g) % c;
count_++;
if (b == base) return count_; //exit_2
}
INT threshold = std::ceil(std::sqrt(c));
std::unordered_map<INT, INT> table;
INT mul = 1;
for (int i = 0; i < threshold; i++, mul = mul * a % c)
table[mul * b % c] = i;
INT a_t = base * mul % c; //base = rec_2, mul = a ^ threshold
for (int i = 1; i <= threshold + 1; i++, a_t = a_t * mul % c) //note the range of i
{
if (table.count(a_t))
return i * threshold - table[a_t] + count_;
}
return -1;
}