注:本篇均为博主个人的理解,如有错误,敬请斧正。
BSGS是用来解决这样一个问题: ,求最小的非负整数x。
BSGS是一种O( )的算法。它有解的条件是模数 为质数并且底数 与模数 互质。
它的过程如下:
令
,
而不是
可以避免用扩欧,如果是
的话要用到扩欧。其中
,则有
。
的用处就在这里,如果是
,那么我们要把
看作一个整体
,那么有
,可以用扩展欧几里得求出
。但是-j的话我们可以把上面的式子移项,可以得到:
。其中
,
,
都是已知的,
可以由
算出来,那么只有式子中
和
是未知的,那么我们考虑枚举
和
。
那么我们会面临这样的问题:为什么将m取为
?i和j要枚举到什么时候结束?
我们考虑模意义下会出现循环节,所以不需要无线枚举下去,那么多长一定会出现循环节呢?(我觉得有点类似pollard_rho的那个rho)我们发现BSGS有解的条件与费马小定理的适用条件一样,这是因为找循环节时用到了费马小定理来证明。
我们考虑证明
由费马小定理可知:当 为质数并且 与 互质时
于是就有了
那么我们可以得知 只需枚举到 即可,由于 ,所以i和j最大也是 。
那么,我们开始从 到 枚举 ,用哈希表记录每一个 ,这里允许后面的 覆盖前面的 ,因为 ,所以 越小, 越小,而我们又要求最小的 ,所以要这么操作。
接下来我们开始从 到 枚举 ,计算 ,在哈希表中查找,如果与表中的某个哈希值相同,那么我就计算出现在的 ,就是答案。因为 每增大 , 就会增大 ,而 最大就是 ,所以只要 最小就好。而因为我们减了 ,所以要从 开始枚举 ,否则会出现负数。
接下来是代码(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随机数生成器