BSGS被用于求解离散对数,即同余方程:
\[ A^x\equiv B\pmod{P} \]php
求\(x\)的最小非负整数解。ios
保证\(A\perp P\)(互质)。算法
首先,咱们根据费马小定理,有
\[ A^{P-1}\equiv 1\pmod{P} \]ui
则显然有
\[ A^{x-k(P-1)}\equiv A^x\pmod{P} \]spa
即
\[ A^{x\mod{P-1}}\equiv A^x\pmod{P} \]code
那么显然\(x<P-1\),咱们就获得了一个\(O(P)\)的算法,然而太慢了。get
考虑分块算法,对\(x\)每\(m\)分一块,则有
\[ A^{im-j}\equiv B\pmod{P} \]io
移项整理
\[ \left(A^m\right)^i\equiv A^j B\pmod{P} \]class
那么咱们枚举\(i\),就能够求出\(A^j\)。再对于\(j\in[0,m-1]\)的\(A^j\)存进哈希表/map,就能够获得\(x=im-j\)了。若是不考虑查询哈希表/map的时间,则时间复杂度为\(O(m+\frac{P}{m})\)。stream
那\(m\)应该取何值呢?求\(f(m)=m+\frac{P}{m}\)的驻点:
\[ \frac{\mathbb{d}f(m)}{\mathbb{d} m}=0 \]
即
\[ 1-\frac{P}{m^2}=0 \]
移项整理
\[ m^2=P \]
解得\(m=\sqrt{P}\)。
那么咱们令\(m=\lceil\sqrt{P}\rceil\),就获得了一个\(O(\sqrt{P})\)的算法。
\(-1\)为无解。
ll BSGS(ll a,ll b,ll p){ if(!a)return b?-1:1; if(b==1)return 0; map<ll,ll>mp; ll m=ceil(sqrt(p)),ax=1; for(int i=0;i<m;i++){ mp[ax]=i; ax=ax*a%p; } ll am=pow(a,m,p),aj=am*pow(b,p-2,p)%p; for(int i=1;i<=m;i++){ if(mp.count(aj))return m*i-mp[aj]; aj=aj*am%p; } return -1; }
#include<iostream> #include<cstdio> #include<cmath> #include<map> using namespace std; typedef long long ll; int t,k; ll y,z,p; ll pow(ll a,ll b,ll p){ ll ans=1; while(b){ if(b&1)ans=ans*a%p; a=a*a%p; b>>=1; } return ans; } ll BSGS(ll a,ll b,ll p){ if(!a)return b?-1:1; if(b==1)return 0; map<ll,ll>mp; ll m=ceil(sqrt(p)),ax=1; for(int i=0;i<m;i++){ mp[ax]=i; ax=ax*a%p; } ll am=pow(a,m,p),aj=am*pow(b,p-2,p)%p; for(int i=1;i<=m;i++){ if(mp.count(aj))return m*i-mp[aj]; aj=aj*am%p; } return -1; } int main(){ scanf("%d%d",&t,&k); while(t--){ scanf("%lld%lld%lld",&y,&z,&p); if(k==1)printf("%lld\n",pow(y,z,p)); else if(k==2){ if(y%p==0)printf("Orz, I cannot find x!\n"); else printf("%lld\n",pow(y,p-2,p)*z%p); }else{ ll ans=BSGS(y%p,z%p,p); if(~ans)printf("%lld\n",ans); else printf("Orz, I cannot find x!\n"); } } }