(扩展)大步小步算法 学习笔记

Note:由于网上很多说一套写一套的资料,让我痛苦了整整两天。在结合了一篇优秀的参考资料后,终于研究得差不多了。我觉得不把这篇总结放出来都有点对不起大家,所以特地把这段总结放上来。其实是希望大家能批评指正一下。

大步小步算法

1. 问题

​ 求解关于 x 的方程 axb(modp) ,其中 a p 互质。可能无解。

2. 暴力方法

O(ϕ(n)) 枚举。

3. 大步小步算法(BSGS)
方法

​ 我们可以设 x=At+B t 是我们自己设的一个参数。其中 0B<t 。这样就变成求 A B 了。

aAt+Bb(modp)

​ 由于 a p 互质,因此可以方便地求逆元,容易得到:
aAtb×aB(modp)

​ 因此,对于所有的 b×aB ,我们可以存进一个哈希表。然后枚举左边的 A ,算出 aAt ,看看是否在哈希表中存在。

​ 很明显参数 t 设成 ϕ(c) 是最优的。

​ 这样预处理的时间复杂度为 O(nlogn) ,单次查询的时间复杂度为 O(nlogn)

4. 扩展大步小步算法(BSGSEx)
①问题

​ 求解关于 x 的方程 axb(modc) ,其中 a c 互质。

②方法

​ 考虑把方程转换成 gcd(a,c)=1 的形式。

​ 转换原方程后可以得到这样一个等价的方程(想想扩展欧几里得):

ax+yc=b,yZ

​ 由裴蜀定理,若 (g=gcd(a,m))b ,方程一定无解。

​ 当 gb 时,在方程左右两边同时除以 g ,得到:

agax1+cgy=bg

​ 相当于得到了模方程=

agax1bg(modcg)

注意,这里 g 不能乘过去了。

​ 令 c=cg b=bg(ag)1 ,得新的方程:

axb(modc)

​ 可知 x=x+1

​ 由于 a c 只有 c 变了,所以可能会有多次过程,不断递归,直到可以用大步小步算法即可。特别地,当 b 在某一时刻为 1 时,我们实际上已经得到了唯一解 x=0

③实际操作

​ 我们可以用一些奇技淫巧避免求逆元。

(1)对于 BSGS

​ 我们可以设 x=AtB 。其中 0B<t 。这样就变成了:

aAtBb(modp)aAtb×aB(modp)

​ 可以避免求逆元。 但是从上式推导到下式是要用到逆元的,因此逆元必须存在。

t 要从 1 枚举到 threshold+1

(2)对于 BSGSEx

agax1bg(modcg)

​ 由于上式的存在,若要递归进行 BSGSEx,我们难以避免求逆元。 所以我们可以将整个过程改成非递归的。这就要求我们记录一些东西。

​ 设原式为 axb(modc) 。每进入一层新的递归,我们都会改变 x b c 。对于 x ,我们只会进行减一的操作,所以这个很好维护。对于 c ,我们也只需要让 c 不断除以 (a,c) 即可,因为我们只会最后用 c 。稍微复杂点的是 b b 除了要除以 (a,c) ,还要乘以 ag 的逆元。由于每次的操作都是类似的,我们可以保存每次的 ag 的积(而不是逆元的积)。

​ 因此我们最后的公式看上去是这样的:(两个 rec 代表额外记录的值)

axrec1(rec2)1b(modc)

rec2 始终是模 c 意义下的。不难发现,当 rec2=b 时, xrec1 有值 0

​ 可以变形得:

(rec2)axrec1b(modc)

​ 然后就可以套用大步小步算法了。设 x=xrec1=AtB
(rec2)aAtBb(modc)(rec2)aAtb×aB(modc)

​ 枚举 A ,计算 (rec2)aAt ,在保存了 b×aB 的哈希表中查找即可。

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;
}

裸题传送门(SPOJ)

猜你喜欢

转载自blog.csdn.net/lycheng1215/article/details/79047734
今日推荐