• 「算法笔记」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。

    转载请注明原文链接
  • 相关阅读:
    虚拟化资料
    Windows线程+进程通信
    Linux进程+进程间通信IPC
    COM/DOM/COM+
    C# 2.0新特性与C# 3.5新特性[转]
    [CruiseControl]配置文件config.xml
    C#的Delegate和Event
    [BuildRelease Management]Visual Build
    软件培训机构
    可以自动输入密码的Runas
  • 原文地址:https://www.cnblogs.com/maoyiting/p/14406472.html
Copyright © 2020-2023  润新知