首页 > 其他分享 >【学习笔记】BSGS与原根学习笔记

【学习笔记】BSGS与原根学习笔记

时间:2023-01-28 14:11:42浏览次数:38  
标签:原根 int dfrac varphi pmod 笔记 BSGS equiv

参考资料:OI-Wiki

\(\text{BSGS}\) (\(\text{Big-Step-Giant-Step}\))

最基本的线性同余方程:

\[ax\equiv b\pmod p \]

可以转化成不定方程:

\[ax+py=b \]

使用扩展欧几里得算法解出 \(x\) 值得解。


而面对形如:

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

求指数方程,在保证 \(\gcd(a,p)=1\) 的情况下,需要用到 \(\text{BSGS}\)。

思考暴力的做法,可以直接暴力枚举左侧的指数,而右侧没有任何变化。

使用类似 \(\text{meet-in-the-middle}\) 的优化策略,将 \(x\) 表示为 \(A\left\lfloor\sqrt{p}\right\rfloor-B\),这样得到新的形式:

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

左侧指数与右侧分别只有 \(O(\sqrt{p})\) 种取值,可以预处理出右侧的所有取值,再枚举左侧取值并套用 unordered_map 判断是否存在。

注意到这里移项成立的充要条件是 \(a^B\) 存在模 \(p\) 意义下的逆元,可得条件应为 \(\gcd(a,p)=1\)。

点击查看代码
int a,b,p;

unordered_map<int,int> mp;

inline void BSGS(){
    if(1%p==b%p) return printf("0\n"),void(); 
    int B=sqrt(p),cntB=(p-1)/B+1;
    int bs=1,now=b;
    for(int i=0;i<B;++i){
        mp[now]=i;
        bs=1ll*bs*a%p,now=1ll*now*a%p;
    }
    now=bs;
    for(int i=1;i<=cntB;++i){
        if(mp.count(now)){
            int ans=i*B-mp[now];
            printf("%d\n",ans);
            return;
        }
        now=1ll*now*bs%p;
    }
    printf("no solution\n");
}

int main(){
    p=read(),a=read(),b=read();
    BSGS();
    return 0;
}

\(\text{exBSGS}\)

扩展算法就是考虑不互质的情况。

不互质时,设 \(\gcd(a,p)=d\),我们同时消去一个 \(d\),得到:

\[\dfrac{a}{d}\times a^{x-1}\equiv \dfrac{b}{d}\pmod {\dfrac{p}{d}} \]

当然这步成立的前提是 \(d\mid b\),这个方程的底数仍为 \(a\),模数发生了改变,但仍不保证二者互质,继续进行这个操作,直到 \(\gcd\left(a,\dfrac{p}{\prod_{i=1}^k d_i}\right)=1\),方程就变成:

\[\dfrac{a^k}{\prod_{i=1}^k d_i}\times a^{x-k}\equiv \dfrac{b}{\prod_{i=1}^k d_i}\pmod {\dfrac{p}{\prod_{i=1}^k d_i}} \]

此时左侧的系数显然和模数互质,是存在逆元的,移项过去之后只需解:

\[a^{x-k}\equiv \dfrac{b}{a^k}\pmod {\dfrac{p}{\prod_{i=1}^k d_i}} \]

套用 \(\text{BSGS}\) 即可。

注意到原方程的解是由此方程的解加上 \(k\) 而得,因此 \([0,k)\) 部分无法计算到,只需要在刚开始每次消去 \(d\) 时暴力判断一下。

点击查看代码
int a,b,p;

inline int q_pow(int A,int B,int MOD){
    int res=1;
    while(B){
        if(B&1) res=1ll*res*A%MOD;
        A=1ll*A*A%MOD;
        B>>=1;
    }
    return res;
}
int gcd(int A,int B){
    if(!B) return A;
    return gcd(B,A%B);
}
int exgcd(int A,int B,int &X,int &Y){
    if(!B){
        X=1,Y=0;
        return A;
    }
    int GCD=exgcd(B,A%B,Y,X);
    Y-=A/B*X;
    return GCD;
}
unordered_map<int,int> mp;
inline void BSGS(int A,int B,int P,int K){
    if(1%P==B%P) return printf("%d\n",K),void(); 
    mp.clear();
    int siz=sqrt(P),cnt=(P-1)/siz+1;
    int bs=1,now=B;
    for(int i=0;i<siz;++i){
        mp[now]=i;
        bs=1ll*bs*A%P,now=1ll*now*A%P;
    }
    now=bs;
    for(int i=1;i<=cnt;++i){
        if(mp.count(now)){
            int ans=i*siz-mp[now]+K;
            printf("%d\n",ans);
            return;
        }
        now=1ll*now*bs%P;
    }
    printf("No Solution\n");
}
inline void exBSGS(int A,int B,int P){
    int D=1,K=0;
    int now=1;
    while(1){
        int d=gcd(A,P);
        if(d==1) break;
        if(now==b) return printf("%d\n",K),void();
        now=1ll*now*a%p;
        ++K;
        D=D*d,P/=d;
        if(B%D) return printf("No Solution\n"),void();
    }
    int pw=q_pow(A,K,P),inv,Y;
    exgcd(pw,P,inv,Y);
    inv=(inv%P+P)%P;
    BSGS(A,1ll*B*inv%P,P,K);
}

int main(){
    // freopen("exbsgs.in","r",stdin);
    // freopen("exbsgs.out","w",stdout);
    while(1){
        a=read(),p=read(),b=read();
        if(!a&&!p&&!b) break;
        b%=p;
        exBSGS(a,b,p);
    }
    return 0;
}

阶与原根

定义和性质

关于阶的性质在此不多赘述,只需知道 \(n\) 为 \(a\) 模 \(m\) 的阶,表示 \(n\) 为最小正整数使得 \(a^n\equiv 1\pmod m\),记作 \(\delta_{m}(a)\)。

定义原根 \(g\) 为满足 \(\gcd(g,m)=1\) 且 \(\delta_{m}(g)=\varphi(m)\) 的正整数。

原根最有用的性质是:当 \(m\) 为质数时,对于 \(i\in [0,m)\),\(g^i\bmod m\) 两两不同且取遍 \([0,m)\)。

原根存在定理

一个数 \(m\) 存在原根,当且仅当 \(m=2,4,p^{\alpha},2p^{\alpha}\),其中 \(p\) 是不为 \(2\) 的素数。

证明略。

原根的个数

由于 \(g^k\) 在模 \(m\) 下可以表示任意一个数,于是有:

\[\delta_{m}(g^k)=\dfrac{\delta_{m}(g)}{\gcd(\delta_{m}(g),k)}=\dfrac{\varphi(m)}{\gcd(\varphi(m),k)} \]

(这里运用了阶的性质。)

于是当且仅当 \(\gcd(\varphi(m),k)=1\) 时,\(g^k\) 同为原根。

由欧拉定理知,\(g\) 的 \([1,\varphi(m)]\) 次幂在模 \(m\) 下形成循环节,于是有意义的原根均在 \([1,\varphi(m)]\) 中,这之间与 \(\varphi(m)\) 互质的 \(k\),自然有 \(\varphi(\varphi(m))\) 个。

原根判定定理

用通俗易懂的话来描述原根:

由于 \(\gcd(g,m)=1\),因此 \(g^{\varphi(m)}\equiv 1\pmod m\) 一定成立,如果 \(\varphi(m)\) 同时是最小的满足此条件的正整数(也就是阶),那么 \(g\) 就是原根。

所以判定原根实际上是在保证互质的基础上判定最小,也就是任意 \(\varphi(m)\) 的一个真因子 \(d\) 都不能使 \(g^{d}\equiv 1\pmod m\)。

一个简洁而快速的检查方法是,令 \(d\) 取遍 \(\varphi(m)\) 除去一个质因子后的值,这样即可覆盖所有的真因子。

求底数的同余方程

形如:

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

其中 \(p\) 是质数的同余方程。

因为 \(p\) 是质数也就一定有原根 \(g\),也就可以表示任意一个 \(x\),即 \(x\equiv g^k\pmod p\)。

于是有:

\[\begin{aligned} (g^k)^a&\equiv b\pmod p\\ (g^a)^k&\equiv b\pmod p \end{aligned}\]

这样求底数的问题就被转化成求指数了,并且模数是质数,可以直接 \(\text{BSGS}\) 计算。

点击查看代码
int a,b,p;
int phi,g;
vector<int> pr;
inline void get_pr(){
    int tmp=phi;
    for(int i=2;i*i<=tmp;++i){
        if(tmp%i==0){
            pr.push_back(i);
            while(tmp%i==0) tmp/=i;
        }
    }
    if(tmp!=1) pr.push_back(tmp);
}
inline int q_pow(int A,int B,int P){
    int res=1;
    while(B){
        if(B&1) res=1ll*res*A%P;
        A=1ll*A*A%P;
        B>>=1;
    }
    return res;
}
unordered_map<int,int> mp;
vector<int> ans;
inline void BSGS(int A,int B,int P){
    if(1%P==B%P) ans.push_back(0);
    int siz=sqrt(P),cnt=(p-1)/siz+1;
    int bs=1,now=B;
    for(int i=0;i<siz;++i){
        mp[now]=i;
        bs=1ll*bs*A%P,now=1ll*now*A%P;
    }
    now=bs;
    for(int i=1;i<=cnt;++i){
        if(mp.count(now)) ans.push_back(i*siz-mp[now]);
        now=1ll*now*bs%P;
    }
}

int main(){
    p=read(),a=read(),b=read();
    phi=p-1;
    get_pr();
    for(g=2;;++g){
        bool pd=1;
        for(int i:pr){
            if(q_pow(g,phi/i,p)==1){
                pd=0;
                break;
            }
        }
        if(pd) break;
    }
    BSGS(q_pow(g,a,p),b,p);
    for(int i=0;i<ans.size();++i){
        int now=ans[i];
        ans[i]=q_pow(g,now,p);
    }
    sort(ans.begin(),ans.end());
    ans.erase(unique(ans.begin(),ans.end()),ans.end());
    printf("%d\n",(int)ans.size());
    for(int i:ans) printf("%d ",i);
    printf("\n");
    return 0;
}

标签:原根,int,dfrac,varphi,pmod,笔记,BSGS,equiv
From: https://www.cnblogs.com/SoyTony/p/Learing_Notes_about_BSGS_and_Primitive_Root.html

相关文章

  • SpringBoot2课程笔记
    学完了SSM系列后,可以学习SpringBoot系列内容了。课程选择了【尚硅谷】SpringBoot2零基础入门教程进行学习。同属于尚硅谷课程,衔接学习顺畅,雷凤阳老师主讲。课程26小......
  • ubuntu20.04根目录扩容笔记
    1、物理扩容(宿主机操作:以vm为例)​点击【虚拟机】–【设置】–【硬盘】–【扩展】–填写扩展大小。​2、分区设置(虚拟机内操作)​(1)查看当前磁盘状态:df-h,根目录空间为58G​(2......
  • 数论笔记3-素因数分解式和取整函数
    1.素因数分解式的性质在第一篇里面我们证明了算术基本定理.下面我们对素因数分解式进行更细致的考察.首先我们对分解式中相同的素数进行合并,得到\(a=p_1^{\alpha_1}......
  • 专利的优先权——学习笔记
    首先要说明专利这东西及其复杂,如果要搞专利最好找个专业的人士,我这也是工作中设计了一些专利方面的东西,于是就涉猎了一下,写写学习笔记,因此本文内容正确与否无法保证,只是个人......
  • 读Java8函数式编程笔记03_高级集合类和收集器
    1. 方法引用1.1. 一种引用方法的轻量级语法1.1.1. 提供了一种简短的语法1.1.2. 标准语法为Classname::methodName1.2. 凡是使用Lambda表达式的地方,就可以使用1.......
  • 笔记
    简谐振动一些定义恢复力:物体离开平衡位置时受到的指向平衡位置的力。与简谐振动相关的位移,在这里统一认为是相对于平衡位置的位移。振幅:物体离开平衡位置的最......
  • 【PyTorch学习笔记】2.pytorch安装问题汇总
    1、为什么有两个同名环境?如图,有两个py_study环境,它们的所在路径仅有一个字母大小写的差异。经过网络搜索,在https://www.cnblogs.com/konosubaakua/p/14761257.html找到......
  • 学习笔记-平衡树
    BST平衡树是为了维护二叉搜索树的“平衡”而存在的BST(二叉搜索树)的定义如下:空树是二叉搜索树。若二叉搜索树的左子树不为空,则其左子树上所有点的附加权值均小于其......
  • JavaScript学习笔记—正则表达式
    用来定义一个规则通过这个规则计算机可以检查一个字符串是否符合规则或者将字符串中符合规则的内容提取出来也是JS中的一个对象,所以要使用正则表达式,需要先创建正则表达......
  • CMU15-445:Lecture #08 笔记
    Lecture#08:TreeIndexes&B+Tree本文是对CMU15-445课程第5节笔记的一个粗略总结和翻译。仅供个人(M1kanN)复习使用。目录Lecture#08:TreeIndexes&B+Tree1.Tab......