1. 程式人生 > >BSGS+原根+離散對數

BSGS+原根+離散對數

前提知識:尤拉定理

這個東西有點強啊。

離散對數就是求給定a,b,p
讓你求 a^x ≡b mod(p) 意義下的 這個x。

這個東西我們用BSGS演算法有點牛逼的名字(拔山蓋世,北上廣深)

我們令x=i*c+j , c=ceil(sqrt(p)) 也就是根號p向上取整。
可以證明如果x有解,那麼x一定小於p因為根據尤拉定理,
a^φ[p] ≡ 1 Mod(p)
而φ[p]是一定小於p的所以如果有解那麼解一定在p之內,
所以可以知道i一定小於c,j也小於c。
那麼就有
a^(i*c+j) ≡ b mod(p)

我們列舉i,那麼我們的目標就是能否找到一個j使得原來的式子成立,我們想到了什麼,沒錯就是hash,我們最開始存入每一個a^j次方一直到c,因為j的範圍為c。那麼我們的目標就是看看hash表裡有沒有一個這樣的數能讓原來的式子成立,這個數我們算算出來就是
a^i 在 mod(p)意義下的逆元乘上b,然後查一查就好了。
總時間複雜度O(sqrt(p))

程式碼解釋說明:
這份程式碼是來求這樣一個問題的:
這裡寫圖片描述

這個要用到一個式子我用ind(x)表示在原根g下mod (p)的離散對數
x=g^ind(x) mod(p)
下面我會說明,求離散對數的方程我在程式碼裡標註了。
程式碼如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<map>
#include<algorithm>
#include<cmath>
#define N 100005
#define PN "modlog"
#define ll long long
#define inf 0x7fffffff #define S 100007 using namespace std; struct Hash{ int head[S],dest[S][2],last[S],etot; void init(){ memset(head,0,sizeof(head)); etot=0; } void add(int a,int b){ int key=a%S; for(int t=head[key];t;t=last[t]) if(dest[t][0]==a)return
; etot++; dest[etot][0]=a; dest[etot][1]=b; last[etot]=head[key]; head[key]=etot; } int find(int a){ int key=a%S; for(int t=head[key];t;t=last[t]) if(dest[t][0]==a)return dest[t][1]; return -1; } }hash; ll x,y,prime[N],ptot,isnot[N],t,type,a,b,p,g,ppp; void pre(){ for(ll i=2;i<N;i++){ if(isnot[i]==0) prime[++ptot]=i; for(ll k=1;k<=ptot&&prime[k]*i<N;k++){ isnot[prime[k]*i]=1; if(i%prime[k]==0)break; } } } ll exgcd(ll a,ll b,ll &x,ll &y){ if(b==0){ x=1;y=0; return a; } ll d=exgcd(b,a%b,x,y); ll t=x; x=y; y=t-a/b*y; return d; } ll ipow(ll a,ll b,ll p){ ll rt=1; for(;b;b>>=1,a=a*a%p) if(b&1) rt=rt*a%p; return rt; } bool jfc(ll a,ll b,ll c){ ll d=exgcd(a,b,x,y); if(c%d)return false; ll t=b/d; if(t<0)t=-t; ppp=t; x*=(c/d); x=(x%t+t)%t; return true; } ll inverse(ll a,ll m){ ll d=exgcd(a,m,x,y); if(d!=1)return -1; return (x%m+m)%m; } ll ind(ll a,ll b,ll m){//求離散對數的函式 ll c=(ll)ceil(sqrt(m)+1); hash.init(); ll cur=1; for(ll i=0;i<c;cur=cur*a%m,i++){ hash.add(cur,i); if(b==cur)return i; } ll base=inverse(cur,m); cur=base*b%m; for(ll i=c;i<=m;i+=c,cur=cur*base%m){ int j=hash.find(cur); if(j!=-1)return i+j; } return -1; } ll get_g(ll m){//求原根 ll p=m-1; for(ll i=2;i<m;i++){ ll k; for(k=1;k<=ptot&&prime[k]*prime[k]<=p;k++) if(p%prime[k])continue; else if(ipow(i,p/prime[k],m)==1)break; if(prime[k]*prime[k]>p)return i; } } int main(){ freopen(PN".in","r",stdin); freopen(PN".out","w",stdout); pre(); cin>>t>>type; while(t--){ cin>>a>>b>>p; g=get_g(p); ll indb=ind(g,b,p),k=p-1; if(indb==-1&&b==0){ if(type==1) printf("1 0\n"); else printf("1\n"); continue; } if(jfc(a,k,indb)==0){ if(type==1) printf("0 0\n"); else printf("0\n"); continue; } else{ ll ans=(k-x)/ppp+1; if((k-x)%ppp==0)ans--; ll y=ans-1; ll q1=ipow(g,ppp,p); ll bg=ipow(g,x,p); ll ans1=ipow(q1,y+1,p); ans1--; ll ttt=inverse(q1-1,p); ans1=ans1*ttt%p; if(y==0)ans1=1; ans1=ans1*bg%p; if(type==1) cout<<ans<<' '<<ans1<<endl; else cout<<ans<<endl; } } }

Definition (階)
給定一個與m(1 m) 互素的a, 則最小的一個滿足:
a^r ≡ 1 (mod m)
的正整數r 叫做a 模m 的階, 這裡記作r =階 (a)(打不出特殊字元)
Definition (元根)
對於模數m, 如果存在一個數g, 滿足:
階(g) = φ(m)
我們則稱g 為模m 的一個元根

套用的pdf上的說明0 0

原根有一個很牛的性質,他在mod (m)意義下的各種次方,可以表示完m的縮系裡的所有數,也就是φ(m)裡的所有數。
那麼我們求出它就可以來幹很牛逼的事情。可以參考上面的題目。
首先 有個定理尤拉定理是a^φ[p] ≡ 1 Mod(p)
求原根我們列舉數字a(汗沒錯就是列舉)因為原根一般都很小,從小到大列舉,然後我們用(φ(m))去除以它的每個質因子得到的數記為qi,判斷a^qi 是否mod m為1 如果是那麼這個列舉的a就不行,否則我們判斷完所有的質因子都mod m不為1那麼就可以。
原因:因為比φ(m)都小的數包含了其他所有質因子mod m都不為1那麼階就是φ(m)。
上面程式碼裡有求原根。