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)。
上面程式碼裡有求原根。