「算法笔记」BSGS

一、离散对数

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

(displaystyle a^xequiv bpmod m)

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

二、BSGS

离散对数:求解关于 (x) 的方程 (a^xequiv bpmod m)

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

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

(x=qt+r)(0leq r<t)),预处理所有 (a^{qt})(m) 的值,存到 Hash 表 / map 中。询问时,枚举 (r),因为 (a^{qt+r}equiv bpmod mLeftrightarrow a^{qt}equiv bcdot a^{-r}pmod m),所以判断是否存在 (a^{qt}equiv bcdot 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)(0leq q,r<t)), (a^{qt-r}equiv bpmod mLeftrightarrow a^{qt}equiv bcdot a^rpmod m),预处理 (bcdot 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 mul(int x,int n,int mod){
    int ans=mod!=1;
    for(x%=mod;n;n>>=1,x=x*x%mod)
        if(n&1) ans=ans*x%mod;
    return ans;
}
int BSGS(int a,int b,int p){    //设 x=qt-r,预处理 b*(a^r)%p,枚举 q,判断是否存在 a^{qt}%p=b*{a^r}%p 
    a%=p,b%=p,mp.clear();
    if(!a) return !b?0:-1;
    int t=ceil(sqrt(p)),x=b;
    for(int i=0;i<=t;i++) mp[x]=i,x=x*a%p;    //枚举 r,预处理 b*(a^r)%p 的值 
    a=mul(a,t,p),x=a;
    for(int i=1;i<=t;i++){    //枚举 q 
        if(mp.count(x)) return i*t-mp[x];    //判断是否能找到对应的 r。若找到了,则 x=qt-r 为 x 的一个解 
        x=x*a%p;
    }
    return -1;
} 
signed main(){
    scanf("%lld%lld%lld",&p,&a,&b);
    ans=BSGS(a,b,p);
    if(~ans) printf("%lld
",ans);
    else puts("no solution");
    return 0; 
}

二、exBSGS

求解关于 (x) 的方程 (a^xequiv bpmod m)(m) 可取任意数。

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

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

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

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

(displaystylefrac{a}{d}cdot a^{x-1}+kcdotfrac{m}{d}=frac{b}{d})

此时,若 (gcd(a,m) eq 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),原式就是:

(displaystylefrac{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;
map<int,int>mp; 
int a,b,p,ans;
int mul(int x,int n,int mod){
    int ans=mod!=1;
    for(x%=mod;n;n>>=1,x=x*x%mod)
        if(n&1) ans=ans*x%mod;
    return ans;
}
int BSGS(int a,int b,int p,int ad){
    a%=p,b%=p,mp.clear();
    if(!a) return !b?0:-1;
    int t=ceil(sqrt(p)),x=b;
    for(int i=0;i<=t;i++) mp[x]=i,x=x*a%p;
    a=mul(a,t,p),x=a*ad%p;    //upd: 原来的 x=a 改为了 x=a*ad%p
    for(int i=1;i<=t;i++){
        if(mp.count(x)) return i*t-mp[x];
        x=x*a%p;
    }
    return -1;
} 
int exBSGS(int a,int b,int p){
    a%=p,b%=p;
    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
",ans);
        else puts("No Solution");
    }
    return 0; 
}

建议把 map 改成 Hash 表 QAQ。

转载请注明原文链接
原文地址:https://www.cnblogs.com/maoyiting/p/14406472.html