省選演算法學習-BSGS與exBSGS
前置知識
擴充套件歐幾里得,快速冪
都是很基礎的東西
擴充套件歐幾里得
說實話這個東西我學了好幾遍都沒有懂,最近終於搞明白,可以考場現推了,故放到這裡來加深印象
翡蜀定理
方程$ax+by=gcd(a,b)$一定有整數解
證明:
因為$gcd(a,b)=gcd(b,a$ $mod$ $b)$
所以假設我們已經求出來了$bx+(a$ $mod$ $b)y=gcd(b,a$ $mod$ $b)$的一組整數解$(p,q)$
因為$a$ $mod$ $b=a-(\lfloor \frac{a}{b} \rfloor \ast b)$
所以$bp+(a-a/b\ast b)q=ax+by$
$b(p-(a/b)q)+aq=ax+by$
所以$x=q,y=(p-(a/b)q)$是一組合法的解
所以我們可以遞迴$gcd$的過程中倒著算每一層的解
當$b=0$時的解為$x=1,y=0$
BSGS
問題提出
給定$a,b,p$,求最小的$x$,使得$a^x≡b(mod$ $p)$
問題求解
顯然這個東西不能直接做
考慮分塊的思想
定義$m=sqrt(p)$
設$x=i\ast m - j$
也就是$a^{i\ast m}≡a^j\ast b(mod$ $p)$
那麼我們首先把$j=0...m-1$時的$a^j\ast b$插入一個雜湊表
然後我們列舉$i$,在雜湊表裡面查詢$a^{i\ast m}$有沒有出現過,如果出現過,它最大的$j$是多少
然後就可以在$O(sqrt(p))$的時間內解決這個問題了
放個板子
namespace hash{ ll first[1000010],next[1000010],val[1000010],hash[1000010],mod=926081,cnt=0; void init(){memset(first,0,sizeof(first));cnt=0;} void insert(ll w,ll pos){ ll p=w%mod,u; for(u=first[p];u;u=next[u]){ if(hash[u]==w){val[u]=pos;return;} if(!next[u]) break; } if(!next[u]){ cnt++; if(!first[p]) first[p]=cnt; else next[u]=cnt; val[cnt]=pos;hash[cnt]=w;next[cnt]=0; } } ll find(ll w){ ll p=w%mod,u; for(u=first[p];u;u=next[u]){ if(hash[u]==w) return val[u]; } return -1; } } ll qpow(ll a,ll b,ll p){ ll re=1; while(b){ if(b&1) re=re*a%p; a=a*a%p;b>>=1; } return re; } ll gcd(ll a,ll b){ if(b==0) return a; return gcd(b,a%b); } ll bsgs(ll a,ll b,ll p){ if(b==1) return 0; ll i,j,m=ceil(sqrt((double)p)),tmp=b,cur,base=qpow(a,m,p); hash::init(); for(j=0;j<m;j++){ hash::insert(tmp,j); tmp=tmp*a%p; } tmp=1; for(i=m;i<=p;i+=m){ tmp=tmp*base%p; cur=hash::find(tmp); if(~cur) return i-cur; } return -1; }
exBSGS
使用BSGS的時候要求$gcd(a,p)=1$,擴充套件版的exBSGS則不需要
具體操作是這樣的:
除掉公約數
假設$tmp=gcd(a,p)$
那麼$(y\ast tmp)^x=z\ast tmp(mod$ $q\ast tmp)$
其中$y,z,q$是新設出來的量,$y\ast tmp=a$,$z\ast tmp=b$,$q\ast tmp=p$
這一步可以看出,如果$b$不能整除$gcd(a,p)$,那麼一定無解
轉化
把等式兩邊的含$tmp$的東西提取出來,可以得到:
$y^{tmp}=z(mod$ $q)$
然後就可以繼續遞迴下去處理了
程式碼
namespace hash{
ll first[1000010],val[1000010],hash[1000010],next[1000010],cnt=0,mod=926081;
void init(){memset(first,0,sizeof(first));cnt=0;}
void insert(ll w,ll pos){
ll p=w%mod,u;
for(u=first[p];u;u=next[u]){
if(hash[u]==w){val[u]=pos;return;}
if(!next[u]) break;
}
if(!next[u]){
cnt++;
if(!first[p]) first[p]=cnt;
else next[u]=cnt;
next[cnt]=0;val[cnt]=pos;hash[cnt]=w;
}
}
ll query(ll w){
ll p=w%mod,u;
for(u=first[p];u;u=next[u]){
if(hash[u]==w) return val[u];
}
return -1;
}
}
ll qpow(ll a,ll b,ll p){
ll re=1;
while(b){
if(b&1) re=re*a%p;
a=a*a%p;b>>=1;
}
return re;
}
ll gcd(ll a,ll b){
if(b==0) return a;
else return gcd(b,a%b);
}
ll bsgs(ll a,ll b,ll p){
if(b==1) return 0;//不要忘了特判
ll i,j,tmp=1,d=1,cnt=0;
hash::init();
while((tmp=gcd(a,p))!=1){
if(b%tmp) return -1;
cnt++;b/=tmp;p/=tmp;d=d*(a/tmp)%p;//注意這個d的寫法
if(b==d) return cnt;//記得寫這個
}
ll m=ceil(sqrt(double(p))),base=qpow(a,m,p);//注意這兩個東西一定要寫在這裡,不要寫在while上面
tmp=b;
for(j=0;j<m;j++){
hash::insert(tmp,j);
tmp=(tmp*a)%p;
}
for(i=m;i<=p+m;i+=m){//這裡注意p+m,不然的話可能會有少數情況掛掉
d=(d*base)%p;//同時注意這裡的tmp相當於是一開始就是上面的d而不是1,也就是一開始要乘上已經除掉的東西
tmp=hash::query(d);
if(tmp!=-1) return i-tmp+cnt;
}
return -1;
}