一、离散对数

给定 \(a,b,m\),存在一个 \(x\),使得

\(\displaystyle a^x\equiv b\pmod m\)

则称 \(x\)\(b\) 在模 \(m\) 意义下以 \(a\) 为底的 离散对数

二、BSGS

离散对数:求解关于 \(x\) 的方程 \(a^x\equiv b\pmod m\)

基本思想:(假设 \(\gcd(a,m)=1\),那么 \(a\) 在模 \(m\) 意义下存在逆元)

考虑类似分块的一个想法。首先设定一个常量 \(t\)

\(x=qt+r\)\(0\leq r<t\)),预处理所有 \(a^{qt}\)\(m\) 的值,存到 Hash 表 / map 中。询问时,枚举 \(r\),因为 \(a^{qt+r}\equiv b\pmod m\Leftrightarrow a^{qt}\equiv b\cdot a^{-r}\pmod m\),所以判断是否存在 \(a^{qt}\equiv b\cdot a^{-r}\pmod m\) 即可。

预处理的复杂度为 \(\mathcal{O}(\frac{m}{t})\),单次询问的复杂度为 \(\mathcal{O}(t)\)。取 \(t=\sqrt{m}\),则复杂度为 \(\mathcal{O}(\sqrt{m})\)

用 map 会多一个 \(\log\)

不同的写法:如果不想求 \(a^{-r}\),设 \(x=qt-r\)\(0\leq q,r<t\)), \(a^{qt-r}\equiv b\pmod m\Leftrightarrow a^{qt}\equiv b\cdot a^r\pmod m\),预处理 \(b\cdot a^r\)\(m\) 的值,枚举 \(q\),判断是否能找到对应的 \(r\)

//Luogu P3846
#include<bits/stdc++.h>
#define int long long
using namespace std;
map<int,int>mp; 
int a,b,p,ans;
int BSGS(int a,int b,int p){
    a%=p,b%=p,mp.clear();
    if(b==1||p==1) return 0;
    int t=ceil(sqrt(p)),x=b,pw=1;
    for(int i=0;i<t;i++) mp[x]=i,x=x*a%p,pw=pw*a%p;    //存入 b*(a^r) 
    x=1;
    for(int i=1;i<=t;i++){    //pw=(a^t), x=(a^t)^i
        x=x*pw%p;
        if(mp.count(x)) return i*t-mp[x];    //听说写 mp.count(x) 会比 mp[x] 快
    }
    return -1;
} 
signed main(){
    scanf("%lld%lld%lld",&p,&a,&b);
    ans=BSGS(a,b,p);
    if(~ans) printf("%lld\n",ans);
    else puts("no solution");
    return 0; 
}

二、exBSGS

求解关于 \(x\) 的方程 \(a^x\equiv b\pmod m\)\(m\) 可取任意数。

BSGS:\(\gcd(a,m)=1\)\(a\) 在模 \(m\) 意义下存在逆元,可令等式右侧出现 \(a\) 的幂次。

扩展 BSGS:考虑 \(\gcd(a,m)\neq 1\) 的情况。一个想法是,将它转化为 \(\gcd(a,m)=1\)

\(d=\gcd(a,m)\)\(a^x\equiv b\pmod m\Leftrightarrow a^x+km=b\),根据裴蜀定理,方程有解当且仅当 \(\gcd(a,m)\mid b\)。所以,若 \(d\nmid b\),则原方程无解。

否则,令方程两边同时除以 \(d\),得:

\(\displaystyle\frac{a}{d}\cdot a^{x-1}+k\cdot\frac{m}{d}=\frac{b}{d}\)

此时,若 \(\gcd(a,m)\neq 1\),则令 \(x'=x-1,m'=\frac{m}{d},b'=\frac{b}{d}\),重复上述步骤,于是可以一直做下去,直到 \(\gcd(a,m')=1\)

不妨设重复了 \(g\) 次,每次求得的 \(d=\gcd(a,m')\) 分别为 \(d_1,d_2,\cdots,d_g\)。记 \(\prod_{i=1}^g d_i =D\),原式就是:

\(\displaystyle\frac{a^g}{D}\cdot a^{x-g}\equiv \frac{b}{D}\pmod {\frac{m}{D}}\)

那么就令 \(a'=a,b'=\frac{b}{D},m'=\frac{m}{D}\),跑一遍 BSGS 即可(在枚举 \(a^{qt}\) 判断时乘上 \(\frac{a^g}{D}\),对应代码中的 ad。最后答案加上 \(g\) 就行了)。

//Luogu P4195
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a,b,p,ans;
map<int,int>mp; 
int BSGS(int a,int b,int p,int ad){
    a%=p,b%=p,mp.clear();
    //这里不写 b=1 的特判,因为左边乘了 ad 
    int t=ceil(sqrt(p)),x=b,pw=1;
    for(int i=0;i<t;i++) mp[x]=i,x=x*a%p,pw=pw*a%p;
    x=ad;    //x=1 改为 x=ad 
    for(int i=1;i<=t;i++){
        x=x*pw%p;
        if(mp.count(x)) return i*t-mp[x];    //这里写 mp[x] 会 TLE,听说是会新建一个 mp[x]
    }
    return -1;
}
int exBSGS(int a,int b,int p){
    a%=p,b%=p;
    if(b==1||p==1) return 0;
    int g=0,ad=1,d,ans;
    while((d=__gcd(a,p))!=1){
        if(b%d) return -1;
        g++,b/=d,p/=d,ad=(ad*a/d)%p;
        if(ad==b) return g;
    } 
    if(~(ans=BSGS(a,b,p,ad))) return ans+g;
    return -1;
}
signed main(){
    while(~scanf("%lld%lld%lld",&a,&p,&b)){
        if(!a&&!b&&!p) break;
        ans=exBSGS(a,b,p);
        if(~ans) printf("%lld\n",ans);
        else puts("No Solution");
    }
    return 0; 
}

建议把 map 改成 Hash 表 QAQ。

相关文章: