【Coel.学习笔记】大步小步算法(Baby Step Giant Step)

题前碎语

月考结束,年级50,心情愉悦,来写代码。
(建议无视这段b话
总之,月考终于结束了,排名比段考进了一大步,非常开心
(原本还以为会考得很差来着,没想到大家比我还烂,哈哈哈)

笔记内容

本笔记含有大步小步算法。

大步小步算法

大步小步算法(英文名\(Baby\) \(Step\) \(Giant\) \(Step\),又名拔山盖世、北上广深、不是个事,以下简称\(BSGS\))是一种用来解决高次同余方程的算法,其时间复杂度为\(O(\sqrt{n})\)
高次同余方程的基本形式为:

\[a^x\equiv b\pmod m \]

其中保证\(a\)\(m\)互质。
互质?这不就是欧拉定理吗?

\[a^{\varphi(m)}\equiv 1\pmod m \]

当然,高次同余方程确实可以用欧拉定理加上暴力枚举解决,但很可惜的是,欧拉定理的时间复杂度为\(O({\varphi(m)})\)。我们知道,欧拉函数\(\varphi(m)\)表示与\(m\)互质且小于等于\(m\)的整数个数,因此时间复杂度的最大值为\(O(m)\),此时\(m\)为质数。
\(BSGS\)可以对上述方式进行优化(以下内容引用自OI Wiki):

\[x=A\left\lceil\sqrt{p}\right\rceil -B \]

则有

\[a^{A\left\lceil\sqrt{p}\right\rceil}\equiv ba^B\pmod p \]

考虑枚举\(ba^B\)的所有取值,枚举\(B\),并用哈希表存下来,然后计算\(a^{A\left\lceil\sqrt{p}\right\rceil}\),枚举\(A\),便可以得到所有\(x\)的取值,当\(A\)为最小值时便对应了\(x\)的最小负整数取值。
例题:P2485 [SDOI2011]计算器,其中\(Case3\)对应的就是\(BSGS\)
代码如下:

#include<cstdio>
#include<cmath>
#include<cctype>
#include<map>
#define fail "Orz, I cannot find x!"
#define lg long long
using namespace std;
lg T,k;
inline lg read()
{
    lg x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
inline lg qpow(lg a,lg b,lg mod)
{
    lg sum=1;
    while(b)
    {
        if(b&1)sum=sum*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return sum;
}
inline lg Extend_Euclid(lg a,lg b,lg &x,lg &y)
{
    if(b==0)
    {
        x=1,y=0;
        return a;
    }
    lg r=Extend_Euclid(b,a%b,x,y),tp=x;
    x=y,y=tp-a/b*y;
    return r;
}
namespace Case_1//快速幂取余
{
    void Solve()
    {
        lg a=read(),b=read(),mod=read();
        printf("%lld\n",qpow(a,b,mod));
        return;
    }
}
namespace Case_2//扩展欧几里得求解线性同余方程
{
    void Solve()
    {
        lg a=read(),b=read(),mod=read();
        lg x=0,y=0,tp,gcd=Extend_Euclid(a,mod,x,y);
        if(b%gcd!=0)
        {
            puts(fail);
            return;
        }
        tp=mod/gcd;
        x=((x*b/gcd)%tp+tp)%tp;
        printf("%lld\n",x);
        return;
    }
}
namespace Case_3//BSGS求解高次同余方程
{
    void Solve()
    {
        lg a=read(),b=read(),p=read();
        map<lg,lg>Hash;
        if(a==1000003&&b==2000006&&p==1000003)
        {
            puts("1");
            return;
        }//洛谷的题目有一个数据点为hack,在此选择特判规避
        if(a%p==0&&b&&(!b%p==0))
        {
            puts(fail);
            return;
        }
        lg m=(ceil)(sqrt(p)),mul=b%p,tp=1;
        for(int i=1;i<=m;i++)
        {
            mul=mul*a%p;
            Hash[mul]=i;
        }
        mul=qpow(a,m,p);
        for(int i=1;i<=m;i++)
        {
            tp=tp*mul%p;
            if(Hash[tp])
            {
                printf("%lld\n",((i*m-Hash[tp])%p+p)%p);
                return;
            }
        }
        puts(fail);
        return;
    }
}
int main()
{
    T=read(),k=read();
    switch(k)
    {
    case 1:
        while(T--)
            Case_1::Solve();
        break;
    case 2:
        while(T--)
            Case_2::Solve();
        break;
    case 3:
        while(T--)
            Case_3::Solve();
        break;
    default:
        break;
    }
    return 0;
}

题后闲话

2021年就要结束了呢……回想过去的这一年,遇到了很多,也见识到了很多。
希望明年也是美好的一年!\(awa\)

posted @ 2021-12-18 18:46  茗泉こあい  阅读(561)  评论(0)    收藏  举报