高次同余笔记(一):baby-step-giant-step算法

我们来看这个方程:
这里写图片描述
a,b,p为常数且在int内。、p是质数。
这个怎么搞?
首先x的取值肯定在0到p-1之间。
暴搜?肯定超时啊。
优化暴搜?用meet-in-the-middle?
先来说说meet-in-the-middle怎么做。
就是找一个点把[0,p-1]这个区间分成两半(一般找中点),算出前一半塞到hash表里面,再算后一半看看hash表里面有没有。
复杂度大概是上面的暴搜的根号。
其实这个思想是好的,它指导我们发现baby-step-giant-step。
先给出baby-step-giant-step算法的步骤。
令m=ceil(sqrt(p)),把(a^0,0),(a^1,1),…,(a^(m-1),m-1)全部塞到hash表里面。
然后令d=a^m,计算d^0,d^1,…,d^m,对于每一个d^i,有
这里写图片描述
这个东西怎么搞?
用exgcd啊!用exgcd求出来y,然后查询hash表里面有没有y就行了。
如果有y,设对应的指数是k,那么答案就是i*m+k。
如果跑完了都没有,那就无解了。
再回顾这个算法,为什么叫baby-step-giant-step?
因为baby的步长为1,giant的步长为m,然后去凑答案。
这个是什么思想?分块!
把答案分成sqrt(p)块,然后暴力枚举块+解线性同余方程验证块内有无满足题意的解。
有时间再写extend-baby-step-giant-step,这个算法解决了p为合数的状况。或者写写离散对数与原根,它可以解决另一种不同形式的高次同余方程。
附POJ2417BSGS裸题代码(hash表乱写的所以很慢)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
ll a,b,p,hsh[70000][2];
bool ishsh[70000];
void insert(ll x,ll t)
{
    ll p=x*12580%70000;
    while(ishsh[p])p=(p+1)%70000;
    ishsh[p]=1;
    hsh[p][0]=x;
    hsh[p][1]=t;
}
int query(ll x)
{
    ll p=x*12580%70000;
    while(hsh[p][0]!=x&&ishsh[p])
        p=(p+1)%70000;
    if(!ishsh[p])return -1;
    return hsh[p][1];
}
void exgcd(ll a,ll b,ll &d,ll &x,ll &y)
{
    if(!b){x=1;d=a;y=0;}
    else
    {
        exgcd(b,a%b,d,y,x);
        y-=a/b*x;
    }
}
void BSGS()
{
    ll m=ceil(sqrt(p)),d=1,val=1,gcd,x,y,t;
    for(int i=0;i<m;++i)
    {
        insert(val,i);
        val=val*a%p;
    }
    for(int i=0;i<m;++i)
    {
        exgcd(d,p,gcd,x,y);
        x=(b/gcd*x%p+p)%(p/gcd);
        t=query(x);
        if(t!=-1){printf("%I64d\n",i*m+t);return;}
        d=d*val%p;
    }
    printf("no solution\n");
}
int main()
{
    while(~scanf("%I64d%I64d%I64d",&p,&a,&b))
    {
        memset(hsh,0,sizeof hsh);
        memset(ishsh,0,sizeof ishsh);
        BSGS();
    }
}

你可能感兴趣的:(数论)