BSGS学习笔记 数论

注:本篇均为博主个人的理解,如有错误,敬请斧正。

BSGS是用来解决这样一个问题: a x = b ( m o d   c ) ,求最小的非负整数x。

BSGS是一种O( c )的算法。它有解的条件是模数 c 为质数并且底数 a 与模数 c 互质。

它的过程如下:

x = i m j j 而不是 + j 可以避免用扩欧,如果是 + j 的话要用到扩欧。其中 m = c ,则有 a i m j = b ( m o d   c )
j 的用处就在这里,如果是 + j ,那么我们要把 a i m 看作一个整体 D ,那么有 D a j = b ( m o d   c ) ,可以用扩展欧几里得求出 a j 。但是-j的话我们可以把上面的式子移项,可以得到: ( a m ) i = b a j ( m o d   c ) 。其中 a , b , c 都是已知的, m 可以由 c 算出来,那么只有式子中 i j 是未知的,那么我们考虑枚举 i j
那么我们会面临这样的问题:为什么将m取为 c ?i和j要枚举到什么时候结束?
我们考虑模意义下会出现循环节,所以不需要无线枚举下去,那么多长一定会出现循环节呢?(我觉得有点类似pollard_rho的那个rho)我们发现BSGS有解的条件与费马小定理的适用条件一样,这是因为找循环节时用到了费马小定理来证明。

我们考虑证明

a x   m o d ( c 1 ) = a x ( m o d   c )

a x n ( c 1 ) = a x ( m o d   c )

a x a n ( c 1 ) = a x ( m o d   c )

a x [ a ( c 1 ) ] n = a x ( m o d   c )

由费马小定理可知:当 c 为质数并且 a c 互质时

a c 1 = 1 ( m o d   c )
所以
[ a ( c 1 ) ] n = 1 ( m o d   c )

于是就有了
a x = a x ( m o d   c )
证明完毕。
那么我们可以得知 x 只需枚举到 c 即可,由于 m = c ,所以i和j最大也是 c
那么,我们开始从 0 m 枚举 j ,用哈希表记录每一个 b a j ( m o d   c ) ,这里允许后面的 j 覆盖前面的 j ,因为 x = i m j ,所以 j 越小, x 越小,而我们又要求最小的 x ,所以要这么操作。
接下来我们开始从 1 m 枚举 i ,计算 ( a m ) i ( m o d   c ) ,在哈希表中查找,如果与表中的某个哈希值相同,那么我就计算出现在的 x = i m j ,就是答案。因为 i 每增大 1 x 就会增大 m ,而 j 最大就是 m ,所以只要 i 最小就好。而因为我们减了 j ,所以要从 1 开始枚举 i ,否则会出现负数。
接下来是代码(poj2417) `

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
using namespace std;

long long a,b,c;//计算a^x=b(mod c)的最小x 
map<long long,long long> mp;
long long gcd(long long x,long long y)
{
    return y?gcd(y,x%y):x;
}
long long ksm(long long x,long long y,long long mod)
{
    long long res=1;
    while(y)
    {
        if(y&1)
        res=(res*x)%mod;
        x=(x*x)%mod;
        y>>=1;
    }
    return res;
}
int main()
{
    while(~scanf("%lld%lld%lld",&c,&a,&b))
    {
        mp.clear();
        if(gcd(a,c)!=1)//如果a与c不互质,一定无解 
        {
            printf("no solution\n");
            continue;
        }
        long long m=(long long)ceil(sqrt(c)),ans,pd=0;//pd记录是否有解 
        for(int j=0;j<=m;++j)//枚举b*a^j存入哈希表(map) 
        {
            if(j==0)
            {
                ans=b%c;
                mp[ans]=j;
                continue;
            }
            ans=(ans*a)%c;
            mp[ans]=j;//用后来的覆盖前面的,因为j越大,-j越小 
        }
        long long x=ksm(a,m,c);//先计算a^m,因为之后要计算(a^m)^i 
        ans=1;
        for(int i=1;i<=m;++i)
        {
            ans=(ans*x)%c;
            if(mp[ans])//i越小,求出的x越小(j<=m,但是i每+1,x就要+m) 
            {
                x=i*m-mp[ans];
                printf("%lld\n",(x%c+c)%c);
                pd=1;
                break;
            }
        }
        if(!pd)
        printf("no solution\n");
    }
    return 0;
}

PS:第一次用markdown,感觉一开始用真的好难受,但是看上去的效果不错。多亏Mys_C_K教我,不然可能一天都发不出博客,在此感谢Mys_C_K。
题单:
bzoj
poj2417 Discrete Logging
洛谷3846可爱的质数
洛谷2485计算器
洛谷3306SDOI随机数生成器

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/80512244
今日推荐