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


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

A^x = B (mod C)

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

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

 

先分類考慮一下:

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

叫他BSGS:

A一定存在mod C意義下的逆元,所以,A^k也存在。

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

所以,A^(fai(c)+1) = A (mod C) 就重新回去了。

所以,最小的x值,如果有解,必然小於等於fai(c)

枚舉顯然是不可靠的。

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

將A^(0~sqrt(fai(c)) mod C 算出來,加入到一個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轉化為正數!!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM