BSGS和exBSGS—详解

问题

A x ≡ B (   m o d   C ) A^x \equiv B (\bmod C) AxB(modC),求最小的x或输出无解。


暴力

先给出一个定理: A n ≡ A n   m o d   φ ( C ) ≡ B (   m o d   C ) A^n \equiv A^{n \bmod \varphi (C)} \equiv B (\bmod C) AnAnmodφ(C)B(modC)
有了这个定理,我们可以在 [ 0 , φ ( C ) ] [0,\varphi (C)] [0,φ(C)]中枚举 x x x,如果这段区间都无解,那么这个方程无整数解。


BSGS算法

前提: C C C为质数。
BSGS算法是一种用分块优化的暴力。设 m = ⌈ C ⌉ m=\left \lceil \sqrt C \right \rceil m=C
再设 x = i ∗ m − j x=i*m-j x=imj,所以 A x = A i ∗ m / A j A^x = A^{i*m}/A^j Ax=Aim/Aj
上式再化成 A i ∗ m / A j ≡ B (   m o d   C ) A^{i*m}/A^j \equiv B (\bmod C) Aim/AjB(modC)
A i ∗ m ≡ B ∗ A j (   m o d   C ) A^{i*m} \equiv B* A^j(\bmod C) AimBAj(modC)
这样以后,我们枚举 i i i,然后 A j A^j Aj就可以确定一个值 B / A i ∗ m B/A^{i*m} B/Aim。这样还是麻烦了一点,因为与 i i i套上了关系,不方便预处理。不妨把 B ∗ A j B*A^j BAj连起来看,如果存在一个 j j j满足 B ∗ A j = A i ∗ m B*A^j = A^{i*m} BAj=Aim,那么就做了一大半了。因为 x = i ∗ m − j x=i*m-j x=imj,把 i i i j j j带进去就可以求到 x x x了。
至于如何快速得到 A i ∗ m A^{i*m} Aim是否为某个 j j j B ∗ A j B*A^j BAj,这个问题可以提前预处理出来,用一个hash表存贮 h a s h [ B ∗ A j ] = j hash[B*A^j]=j hash[BAj]=j,这个 j j j应该从 0 0 0开始枚举到 m m m。如果 h s a h [ A i ∗ m ] hsah[A^{i*m}] hsah[Aim]不为 0 0 0,则 j = h a s h [ A i ∗ m ] j=hash[A^{i*m}] j=hash[Aim]即为 j j j的解。


exBSGS算法

前提:无
既然没有限制,那就可能不是互质的关系。设 d = gcd ⁡ ( A , C ) d=\gcd(A,C) d=gcd(A,C),再设 A = a ∗ d A=a*d A=ad C = c ∗ d C=c*d C=cd这是一点问题都没有的。如果 B B B不是 d d d的倍数,除非 B B B 1 1 1(此时 x = 0 x=0 x=0)否则无解。所以 B B B也可以表示成 B = b ∗ d B=b*d B=bd
所以上式被表示成 ( a ∗ d ) x ≡ b ∗ d (   m o d   c ∗ d ) (a*d)^x \equiv b*d(\bmod c*d) (ad)xbd(modcd)
去掉一个 d d d,得 a ∗ ( a ∗ d ) x − 1 ≡ b (   m o d   c ) a*(a*d)^{x-1} \equiv b(\bmod c) a(ad)x1b(modc)
也可以写成 ( A 1 / d ) ∗ A x − 1 ≡ b (   m o d   c ) (A^1/d)*A^{x-1} \equiv b(\bmod c) (A1/d)Ax1b(modc)
继续做gcd,每次gcd都会提出一个 ( A / d ) (A/d) (A/d)这样的东西出来。记所有的 d d d之积为 q q q,即 q = ∏ d i q=\prod d_i q=di,做了 n u m num num次后会变成 ( A n u m / q ) ∗ A x − n u m ≡ B / q (   m o d   C / q ) (A^{num}/q)*A^{x-num}\equiv B/q(\bmod C/q) (Anum/q)AxnumB/q(modC/q)
一直做到 gcd ⁡ ( A , C / q ) = 1 \gcd(A,C/q)=1 gcd(A,C/q)=1之后,我们称 A A A C / q C/q C/q互质了,这就满足了BSGS的条件。
其中 A n u m / q A^{num}/q Anum/q可以在gcd时统计,为已知,我们用 D D D来表示它,即 D = A n u m / q D=A^{num}/q D=Anum/q。设 y = x − n u m y=x-num y=xnum B ′ = B / q B'=B/q B=B/q C ′ = C / q C'=C/q C=C/q。因为 q q q也是已知的,所以 B ′ B' B C ′ C' C都是已知的。式子变成了 D ∗ A y ≡ B ′ (   m o d   C ′ ) D*A^y\equiv B'(\bmod C') DAyB(modC)
接下来用BSGS的思路来考虑,设 y = i ∗ m − j y=i*m-j y=imj,那么有 D ∗ A i ∗ m / A j ≡ B ′ (   m o d   C ′ ) D*A^{i*m}/A^j\equiv B'(\bmod C') DAim/AjB(modC)
移项有 D ∗ A i ∗ m ≡ B ′ ∗ A j (   m o d   C ′ ) D*A^{i*m}\equiv B'*A^j(\bmod C') DAimBAj(modC)
为了解决这个式子,在一开始 i = 0 i=0 i=0时,左边就为 D D D。最后用BSGS我们求到的是 i i i j j j,这样用 y = i ∗ m − j y=i*m-j y=imj可以求到 y y y,进而根据 y = x − n u m ⇒ x = y + n u m y=x-num\Rightarrow x=y+num y=xnumx=y+num可以求到 x x x x = i ∗ m − j + n u m x=i*m-j+num x=imj+num


再补讲一个细节吧,我们的 D D D是随gcd实时更新的,也就是 D ∗ A x − n u m ≡ B / q (   m o d   C / q ) D*A^{x-num}\equiv B/q(\bmod C/q) DAxnumB/q(modC/q)
如果某一时刻出现了 D = B / q D=B/q D=B/q的情况,也就是说式子变成了 D ∗ A x − n u m ≡ D (   m o d   C / q ) D*A^{x-num}\equiv D(\bmod C/q) DAxnumD(modC/q)
那么此时的 A x − n u m A^{x-num} Axnum必须为1,即 x − n u m = 0 x-num=0 xnum=0,所以 x = n u m x=num x=num,同时可以结束exBSGS。


代码

BSGS模版例题 洛谷2485 [SDOI2011]计算器

#include<map>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

map<ll,ll> hash;

ll power(ll a,ll n,ll mod)//a^n%mod
{
    ll re=1;a%=mod;
    while(n)
    {
        if(n&1) re=re*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return re;
}

void bsgs(ll y,ll z,ll p)//y^x=z(mod p)
{
    if(y==0 && z==0){puts("1");return ;}//几句特判 
    if(y==0 && z!=0){puts("Orz, I cannot find x!");return;}
    
    hash.clear();
    ll m=ceil(sqrt(p));
    ll tmp=z%p;hash[tmp]=0;//右边z*A^j,当j=0时为z 
    for(ll i=1;i<=m;i++)
    {
        tmp=tmp*y%p;
        hash[tmp]=i;
    }
    
    ll t=power(y,m,p); 
    tmp=1;//左边y^(i*m),当i=0时为1
    for(ll i=1;i<=m;i++)
    {
        tmp=tmp*t%p;//i每加1,多乘y^(i*m)
        if(hash[tmp])
        {
            ll ans=i*m-hash[tmp];
            printf("%lld\n",(ans%p+p)%p);
            return ;
        }
    }
    puts("Orz, I cannot find x!");
}

int main()
{
    int T,opt;
    ll x,y,p,m,z;
    scanf("%d%d",&T,&opt);
    while(T--)
    {
        scanf("%lld%lld%lld",&y,&z,&p);
        y%=p;
        if(opt==1) printf("%lld\n",power(y,z,p));
        else if(opt==2)
        {
            z%=p;
            if(y==0 && z!=0) puts("Orz, I cannot find x!");
            else printf("%lld\n",z*power(y,p-2,p)%p);//z/y
        }
        else bsgs(y,z,p);
    }
    return 0;
}

exBSGS模板:

#include<map>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

map<ll,ll> hash;

inline ll gcd(ll a,ll b)
{
    ll tmp;
    while(tmp=a%b) a=b,b=tmp;
    return b;
}

inline ll multi(ll x,ll y,ll mod)//快速乘 
{
    ll tmp=x*y-(ll)(((long double)x*y+0.5)/mod)*mod;
    return tmp<0?tmp+mod:tmp;
}

ll power(ll a,int b,ll m)//快速幂 a^b
{
    ll re=1;
    while(b)
    {
        if(b&1) re=multi(re,a,m);
        a=multi(a,a,m);
        b>>=1;
    }
    return re;
}

int main()
{
    ll a,b,m;//a^x=b(mod m)
    scanf("%lld%lld%lld",&a,&b,&m);
    
    ll D=1,num=0;
    while(1)
    {
        ll d=gcd(a,m);
        if(d==1) break;
        if(b%d){puts("INF");return 0;}//b不是d的倍数,无解 
        b/=d;m/=d;
        num++;
        D=multi(D,a/d,m);
        if(D==b){printf("%lld",num);return 0;}//系数等于余数,此时x-num为0 
    }
    
    ll now=b;//当j=0时,式子右边=b 
    hash[now]=0;
    int mm=ceil(sqrt(m));//记得向上取整 
    for(int i=1;i<=mm;i++)
    {
        now=multi(now,a,m);
        hash[now]=i;
    }
    
    now=D;//当i=0时,式子左边=D 
    ll q=power(a,mm,m);//每个i+1,now增大a^mm倍 
    for(int i=1;i<=mm;i++)
    {
        now=multi(now,q,m);
        if(hash[now])
        {
            printf("%lld\n",(((ll)i*mm-hash[now]+num)%m+m)%m);
            return 0;
        }
    }
    puts("INF");//最后再判一次 
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值