前言:
今天更BSGS算法。俗称大步小步算法(Big-Step G…-Step),又称拔山盖世、北上广深、白色狗屎 。
问题:
求解指数同余方程:的最小自然数解。
BSGS算法:模板题
这个算法只考虑的情况。
那么,怎么做呢。
其实做法很简单,就是枚举。
先来一种暴力枚举。
就是枚举从,然后检验答案是否正确。
知道欧拉定理的应该知道只要枚举到,但为质数时欧拉函数仍为。
看过阶与原根的应该知道只要枚举到的阶即可,但算的阶仍然要暴力去枚举。
所以这里只要一个一个枚举过去便好。
但是的暴力枚举配不上这么高大上的名字,所以还有优化的余地。
我们可以考虑折半枚举。
怎么折半,就是这个算法的精髓了。
我们令,其中为我们设置的一个定值,和i均为变量。
这样原式就变为。
由于,我们可以将的逆元乘到右边,变成。
这样我们可以枚举所有的,将其对应右边的值存入哈希表(也可用unordered_map)。然后枚举的过程中,去表中找满足的,找到后就是答案。当然因为要找最小的,枚举过程中动点手脚就好了。
那么这个的设置嘛,利用分块的思想,设为就好了,这样只用枚举到,也只用枚举到。总的复杂度为。
上代码:
#include <bits/stdc++.h>
#include <unordered_map>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll fpow(ll a,ll n,ll mod)
{
ll sum=1,base=a%mod;
while(n!=0)
{
if(n%2)sum=sum*base%mod;
base=base*base%mod;
n/=2;
}
return sum;
}
ll ex_gcd(ll a,ll b,ll& x,ll& y)
{
if(b==0)
{
x=1;y=0;
return a;
}
ll ans=ex_gcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-a/b*y;
return ans;
}
ll inv(ll a,ll mod)//存在逆元条件:gcd(a,mod)=1
{
ll x,y;
ll g=ex_gcd(a,mod,x,y);
if(g!=1)return -1;
return (x%mod+mod)%mod;
}
ll BSGS(ll a,ll b,ll p)
{
b%=p;
if(b==1||p==1)return 0;
ll n=sqrt(p);
static unordered_map<ll,ll>Bmp;
Bmp.clear();
ll inva=inv(fpow(a,n-1,p),p)*b%p;
for(ll i=n-1;i>=0;i--)
{
Bmp[inva]=i;inva=inva*a%p;
}
ll ta=1,powa=fpow(a,n,p);
for(ll k=0;k<=p;k+=n)
{
if(Bmp.count(ta))return k+Bmp[ta];
ta=ta*powa%p;
}
return -1;
}
int main()
{
ll p,b,n;
scanf("%lld%lld%lld",&p,&b,&n);
ll ans=BSGS(b,n,p);
if(ans==-1)printf("no solution\n");
else printf("%lld\n",ans);
return 0;
}
扩展BSGS:模板题
既然BSGS只能解决的情况,那么当然还有可能不等于的情况。
那么这个时候就需要对算法进行扩展了。
因为,所以不能再像原来一样逆元了。
所以我们考虑把先提取出来。
我们令。
我们将原式展开成。
然后同时除以变成。
再同时取模就得到了
容易发现必须是的倍数,否则直接返回无解。
然后此时,所以可以乘上它的逆元。
变成。
就变成了一个新的问题。
那么观察此时是否一定为。
当然不一定,举例,那么,将除以后,仍能除以,没事,那再除一次嘛。
笔者懒,写了递归版,这个过程可以一步到位的,递归可能会慢一些,但是比起固有的复杂度,多个也无伤大雅。
那么递归的思路就很清晰了。
如果,那么用BSGS求解。
如果,那么用上面的办法将模的问题变为模的问题,然后再递归调用这个函数。
非递归思路:
如果,那么用BSGS求解。
如果,那么用上面的方法给除以,只要除以多少个弄清楚,就可以一次弄好了。
最后是我的递归版代码:
#include <bits/stdc++.h>
#include <unordered_map>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll fpow(ll a,ll n,ll mod)
{
ll sum=1,base=a%mod;
while(n!=0)
{
if(n%2)sum=sum*base%mod;
base=base*base%mod;
n/=2;
}
return sum;
}
ll ex_gcd(ll a,ll b,ll& x,ll& y)
{
if(b==0)
{
x=1;y=0;
return a;
}
ll ans=ex_gcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-a/b*y;
return ans;
}
ll inv(ll a,ll mod)//存在逆元条件:gcd(a,mod)=1
{
ll x,y;
ll g=ex_gcd(a,mod,x,y);
if(g!=1)return -1;
return (x%mod+mod)%mod;
}
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
ll BSGS(ll a,ll b,ll p)
{
b%=p;
if(b==1||p==1)return 0;
ll n=sqrt(p);
static unordered_map<ll,ll>Bmp;
Bmp.clear();
ll inva=inv(fpow(a,n-1,p),p)*b%p;
for(ll i=n-1;i>=0;i--)
{
Bmp[inva]=i;inva=inva*a%p;
}
ll ta=1,powa=fpow(a,n,p);
for(ll k=0;k<=p;k+=n)
{
if(Bmp.count(ta))return k+Bmp[ta];
ta=ta*powa%p;
}
return -1;
}
ll exBSGS(ll a,ll b,ll p)
{
b%=p;
if(a==0&&b==0)return 1;
else if(a==0&&b!=0)return -1;
if(b==1||p==1)return 0;
ll d=gcd(a,p);
if(b%d!=0)return -1;
p=p/d;
b=b/d*inv(a/d,p)%p;
if(d!=1)
{
ll ans=exBSGS(a,b,p);
if(ans==-1)return -1;
return ans+1;
}
ll ans=BSGS(a,b,p);
if(ans==-1)return -1;
return ans+1;
}
int main()
{
ll a,p,b;
while(scanf("%lld%lld%lld",&a,&p,&b)!=EOF)
{
if(a==0&&p==0&&b==0)break;
ll ans=exBSGS(a,b,p);
if(ans==-1)printf("No Solution\n");
else printf("%lld\n",ans);
}
return 0;
}
最后再补充一个例题,Mod Tree。注意题目中的取模后相同并非模意义下相同。
高次同余方程:
不知道原根的出门左转。
最后再将一个BSGS的应用。
求解方程。
我们只考虑为素数的求解办法。
做法:
我们先求出的原根。
根据原根的性质,当为素数时,的幂次可以得到一个完全剩余系。
所以我们可以令,同样可以令。
此时,我们只要求解,就可以求解了。
方程变为。
等价于方程。(这个可以根据费马小定理,也可以根据)
因为不能确保,所以不能乘逆元。
其实方程等价于求方程,用扩展欧几里得即可。
到这一步答案就解出来了。
嗯?我要说的BSGS呢?
噢,你会发现这个还没求。
,所以求就是求指数同余方程了。
到这里,求毕。
这个没写代码,我懒了,想都别想。
那么关于不为素数的。
其实我不会,但是我还可以鬼扯,可能可行也可能不行,往下阅读时请保留自己的理智,免得被笔者误导。
考虑分解合数。
噢,好像不可行。
但是当,即每个素数的次数都为次时,还是有办法可行的。
我们只要求解出对于每个模数的方程的解。
然后用中国剩余定理合并即可。
这样想的话,如果有办法求解的话,就可以解决任意模数的问题了,暂时还没想好。
扯毕。
来源:CSDN
作者:Zimba_
链接:https://blog.csdn.net/weixin_43785386/article/details/104108230