BSGS和exBSGS—详解

版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/83513731

问题

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


暴力

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


BSGS算法

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


exBSGS算法

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


再补讲一个细节吧,我们的 D D 是随gcd实时更新的,也就是 D A x n u m B / q ( m o d    C / q ) D*A^{x-num}\equiv B/q(\mod C/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(\mod C/q)
那么此时的 A x n u m A^{x-num} 必须为1,即 x n u m = 0 x-num=0 ,所以 x = n u m 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;
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/83513731