BSGS&EXBSGS 大手拉小手,大步小步走

大步小步走算法處理這樣的問題:

A^x = B (mod C)

求知足條件的最小的x(可能無解)

其中,A/B/C均可以是很大的數(long long之內)

 

先分類考慮一下:node

當(A,C)==1 即A、C互質的時候,ios

叫他BSGS:算法

A必定存在mod C意義下的逆元,因此,A^k也存在。ide

注意到,A^(fai(c)) = 1 (mod c)  ......................(fai(c)表示c的歐拉函數值)函數

因此,A^(fai(c)+1) = A (mod C) 就從新回去了。spa

因此,最小的x值,若是有解,必然小於等於fai(c)code

枚舉顯然是不可靠的。blog

這樣考慮:將對數值分解:x=x1+x2;string

將A^(0~sqrt(fai(c)) mod C 算出來,加入到一個hash表中,hash

這樣,A^x=A^(x1+x2) 讓 x1 取成:0*sqrt(fai(c) ,1*(sqrt(fai(c)) ,2*(sqrt(fai(c)) ... fai(c)

A^(p*sqrt(fai(c))+x2) = B mod C

由於,A^k都存在逆元,因此能夠直接把A^(sqrt(fai(c))逆元預處理出來,再在每次循環p的時候,把A^(p*sqrt(fai(c))除過去;

即:A^x2 = B*ni mod C

對於這個B*ni(取模後),只須要在以前處理的hash表中查一下有沒有出現就能夠、

出現了就對應一個x2,對於x ,就是p*sqrt(fai(c))+x2

不然繼續循環p

爲了保證這個x是最小的x,

咱們在建hash表的時候,是x的值從小到大建,若是這個值以前沒有出現,就插入,不然不進行操做(至關於用小的x覆蓋大的)

②咱們分塊的時候,從小到大枚舉p,因此找到的第一個就是答案。

若是一直沒有找到,就返回無解。

複雜度:O(sqrt(c)) (哈希表用鄰接表掛鏈實現,不用map的log複雜度)

BSGS代碼:poj2417(這個保證了模數是質數(直接用的費馬),可是其實不必定是)

#include<cstdio> #include<algorithm> #include<cmath> #include<iostream> #include<map> #include<cstring>
using namespace std; typedef long long ll; const int N=46349; const int mod=100003; ll p,A,B; ll ni[N]; struct node{ int nxt[mod],val[mod],id[mod],cnt; int hd[mod]; void init(){ memset(nxt,0,sizeof nxt),memset(val,0,sizeof val);cnt=0; memset(hd,0,sizeof hd);memset(id,0,sizeof id); } void insert(ll x,int d){ int st=x%mod; for(int i=hd[st];i;i=nxt[i]){ if(val[i]==x) return; } val[++cnt]=x;nxt[cnt]=hd[st];id[cnt]=d;hd[st]=cnt; } int find(ll x){ int st=x%mod; for(int i=hd[st];i;i=nxt[i]){ if(val[i]==x) return id[i]; } return -233; } }ha; map<ll,int>mp; ll qm(ll a,ll b){ ll ret=1,base=a; while(b){ if(b&1) ret=(ret*base)%p; base=(base*base)%p; b>>=1; } return ret; } ll BSGS(){ ll up=(ll)floor(sqrt(1.0*p-1))+1; cout<<up<<endl; ni[0]=1; for(int i=1;i<=up;i++){ ni[i]=qm(qm(A,i*up),p-2); } for(int i=0;i<up;i++){ ll t=qm(A,i); ha.insert(t,i); } for(int i=0;i<=up;i++){ if(i*up>p-1) break; ll ri=B*ni[i]%p; ll ret=ha.find(ri); if(ret>=0) return i*up+ret; } return -233; } int main() { while(scanf("%lld",&p)!=EOF){ scanf("%lld%lld",&A,&B); ha.init(); ll ret=BSGS(); if(ret==-233){ puts("no solution"); } else{ printf("%lld\n",ret); } } return 0; }
BSGS

 

 

EXBSGS:

若是(A,C)!=1怎麼辦?

轉化成互質的!!

設g=gcd(A,C)
A^x = B mod C

若是B不能被g整除,就break掉;(後面已經沒意義了)

不然同除以g A^(x-1) * A/g = B/g mod C/g

這個是等價的變形。

注意到,A/g C/g 是互質的。

設g=gcd(A, C/g)

循環處理。。。。。

直到g == 1結束。

設進行了num次,如今獲得的等式是:

A^(x-num) * A/πg = B/πg mod C/πg

如今,A和C/πg是互質的了。

A/πg也和C/πg互質,因此直接轉化成逆元,乘過去。

形式是這樣的:

A^x = NB mod C

其中(A,C)=1能夠用BSGS了。

注意:這裏求出來的是x>=num 的最小解

咱們還要暴力枚舉一發x = 0~num

直接經過原式子驗證。

由於num必定是log級別的,因此不費事。

 

EXBSGS代碼:poj3243

#include<cstdio> #include<algorithm> #include<cstdlib> #include<iostream> #include<cstring> #include<cmath>
using namespace std; typedef long long ll; const int N=31630;//sqrt fai()
const int mod=100003; ll C,A,B; ll ni[N]; struct node{ int nxt[mod],val[mod],id[mod],cnt; int hd[mod]; void init(){ memset(nxt,0,sizeof nxt),memset(val,0,sizeof val),memset(id,0,sizeof id); memset(hd,0,sizeof hd),cnt=0; } void insert(ll x,int d){ int st=x%mod; for(int i=hd[st];i;i=nxt[i]){ if(val[i]==x) return; } val[++cnt]=x,nxt[cnt]=hd[st],id[cnt]=d,hd[st]=cnt; } int find(ll x){ int st=x%mod; for(int i=hd[st];i;i=nxt[i]){ if(val[i]==x) return id[i]; } return -233; } }ha; void exgcd(ll a,ll b,ll &x,ll &y){ if(b==0){ x=1,y=0;return; } exgcd(b,a%b,y,x); y-=(a/b)*x; } ll qm(ll a,ll b,ll p){ ll ret=1,base=a; while(b){ if(b&1) ret=(base*ret)%p; base=(base*base)%p; b>>=1; } return ret; } int gcd(int a,int b){ return (b==0)?a:gcd(b,a%b); } int BSGS(ll a,ll b,ll c){ int up=(int)floor(sqrt(1.0*c-1))+1; ll ni=0,yy=0; exgcd(qm(a,up,c),c,ni,yy); ni=(ni%c+c)%c;//warning!!! 必須變成正數 
    ll kk=1; for(int i=0;i<=up-1;i++){ ha.insert(kk,i); kk=(kk*a)%c; } ll bb=b; for(int i=0;i<=up;i++){ int kk=ha.find(bb); if(kk>=0) return i*up+kk; bb=(bb*ni)%c;//不斷找逆元 遞推就能夠 
 } return -233; } int EXBSGS(){ int num=0; int yC=C; int yB=B; int yA=A; ll ji=1; int ret=-233; bool flag=false; while(1){ int g=gcd(A,C); if(g==1) break; if(B%g) { flag=true;break; } B/=g,C/=g,ji=(ji*A/g)%C; num++; } for(int i=0;i<=num;i++){ ll kk=qm(yA,i,yC); if(kk%yC==yB) return i; } if(!flag){ ll ni,yy; exgcd(ji,C,ni,yy); ni=(ni%C+C)%C;//warning!!! 必須變成正數 
        ll NB=(B*ni)%C; ret=BSGS(A,NB,C); } if(ret>=0) return ret+num; else return -233; } int main(){ while(1){ scanf("%lld%lld%lld",&A,&C,&B); if(A==0&&B==0&&C==0) break; ha.init(); B%=C; int ret=EXBSGS(); if(ret>=0){ printf("%d\n",ret); } else{ puts("No Solution"); } } return 0; }
EXBSGS

 

個人易錯點:

①BSGS和EXBSGS中,老是忘了對B或者NB取模,就爆long long 了。(平常模一模)

②C不必定是質數,因此用exgcd求逆元(歐拉定理親測也行,只要你不嫌sqrt麻煩)

③分塊求每一塊大小的時候,up=floor(sqrt(C))+1注意必定要加一,不然floor就卡下去了。對於C是質數,就可能不能取到C-1

好比:73^x = 71 mod 139 (139是質數)的解是:136

若是不加1,up=11 那麼,最多能分塊到:121+11=132 就輸出無解了。

④用exgcd求逆元的時候,必須把求出來的逆元:ni=(ni%C+C)%C轉化爲正數!!!

相關文章
相關標籤/搜索