原根&離散對數簡單總結


原根&離散對數


1.原根

1.定義:

定義\(Ord_m(a)\)為使得\(a^d\equiv1\;(mod\;m)\)成立的最小的d(其中a和m互質)

由歐拉定理可知:
\(Ord\le\Phi(m)\)
\(Ord_m(a)=\Phi(m)時,稱a是模m意義下m的一個原根\)(記住原根是a,不是d!)

2.原根的性質:

1.具有原根的數字僅有以下幾種形式:\(2,4,p^n,2·p^n\)(p是奇質數)

2.一個數的最小原根的大小不超過 \(m^{\frac14}\)

3.若g是m的一個原根,那么\(g^d\)是m的原根的充分必要條件是\(gcd(d,\Phi(m))=1\),
由此可推知一個數的原根個數為\(\Phi(\Phi(m))\)

3.求解原根的基本步驟:

  1. 判斷一個數是否有原根。(通過性質1,枚舉質數即可)
  2. 求得最小原根。(通過性質2,依次枚舉\(2~m^{\frac14}\)判斷即可)
  3. 求出所有原根。(通過性質3,枚舉次數d即可)

代碼實現:
1.篩出質數並進行第一步(順便把歐拉函數也篩出來):

void get_prime()
{
	is_pri[1]=1;
	for(register int i=2;i<N;i++){
		if(!is_pri[i]) Prime[++tot]=i,phi[i]=i-1;
		for(register int j=1;j<=tot;j++){
			if(1ll*i*Prime[j]>=N) break;
			register int res=i*Prime[j];
			is_pri[res]=1;
			if(i%Prime[j]==0){
				phi[res]=phi[i]*Prime[j];
				break;
			}
			phi[res]=phi[i]*phi[Prime[j]];
		}
	}
}
bool judge(int m)//判斷原根有無
{
	if(m==1) return 0;
	if(m==2||m==4) return 1;
	if((m&1)==0) m>>=1;if((m&1)==0) return 0;
	for(int i=2;i<=tot&&(1ll*Prime[i]*Prime[i]<=m);i++)
	{
		if(m%Prime[i]!=0) continue;
		while(m%Prime[i]==0) m/=Prime[i];
		if(m==1) return 1;
		return 0;
	}
	return m;
//這里要return m, 由於Prime[i]*Prime[i]>m即退出的影響
}

2.找出最小原根:

    for(int i=2;1ll*i*i<=phi[m];i++){
	//篩出phi的約數,用於check
		if(phi[m]%i==0){
			st[++top]=i;
			if(i*i!=phi[m]) st[++top]=phi[m]/i;
		}
	}
    int g;
	for(g=2;g<=100;g++)//枚舉最小原根
	{
		if(check(g,m)) break;
	}
bool check(int x,int m)
{
	if(Pow(x,phi[m],m)!=1) return 0;
	for(int i=1;i<=top;i++)if(Pow(x,st[i],m)==1) return 0;
	return 1;
}
int Pow(int x,int n,int mod)
{
	int ans=1;
	while(n){
		if(n&1) ans=(1ll*ans*x)%mod;
		x=(1ll*x*x)%mod;
		n>>=1;
	}
	return ans;
}

3.找出所有原根

    int cnt=0;
	register int res=1;
	for(register int i=1;i<=phi[m];i++)
	//由歐拉定理,只用枚舉到g^phi[m]
	{
		if(cnt==phi[phi[m]]) break;//個數限制
		res=(1ll*res*g)%m;
		if(gcd(i,phi[m])!=1) continue;
		ans[++cnt]=res;
	}
	sort(ans+1,ans+1+cnt);//由於取了模,要sort
	for(register int i=1;i<=cnt;i++)
	{
		if(i>1) putchar(' ');
		printf("%d",ans[i]);
	}
	puts("");

2.離散對數

1.定義

普通對數:

若$$a^x=b$$則稱\(x\)\(b\)\(a\)為底的對數,記做\(x=log_ab\)

離散對數就是把這個放在了模意義下,即求解方程:

\[a^x \equiv b (mod\ p) \]

2.求解方法

方法1-暴力法: 猜測這東西有循環節,直接暴力枚舉x,哈希什么的判循環,循環則無解

方法2-帶有數學知識的暴力:
由歐拉定理:\(a^{\phi (p)} \equiv 1\ (mod\ p)\)
於是我們只需要把\(x\)\(0\)枚舉到\(\phi(p)-1\)就可以了

方法3-正解

BSGS 大步小步算法

這是一種Meet in the middle思想的應用,普通的BSGS只適用於a,p互質的情況,所以我們先討論a,b互質的情況

BSGS:
我們既然是枚舉,那么就有折半枚舉這種思想,這里也類似,對於方程:

\[a^x \equiv b\ (mod\ p) \]

我們可以把\(x\)表示為\(t*u-v\) 的形式,保證\(0<v<u\),然后把左邊的負的乘過去,就是:

\[a^{ t*u } \equiv a^v*b\ (mod\ p) \]

相當於是把一個數給除了過去,所以只能用於a,p互質

於是我們發現如果先算出一系列的v取什么值的時候右邊的值存入哈希表,然后枚舉左邊的t,就可以在哈希表中直接查詢出解了,並且第一個找到的一定是最小的

所以顯然u取\(\sqrt {p-1}\)的時候比較優秀

時間復雜度\(O(\sqrt{p})\),如果手寫哈希的話,用map就多一個log

代碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<queue>
using namespace std;
const int N=1e5+1;
const int mod=1e6+3;
typedef long long ll;
struct hashlist{
	int key[N],next[N],head[mod<<1];
	int cnt;int vb[N];
	inline void insert(int x,int b){
		register int ret=x%mod,p;
		for(p=head[ret];p;p=next[p]){
			if(key[p]==x) return void(vb[p]=max(vb[p],b));
		}
		++cnt;vb[cnt]=b;key[cnt]=x;next[cnt]=head[ret];head[ret]=cnt;
		return;
	}
	inline int find(int x){
		register int ret=x%mod,p;
		for(p=head[ret];p;p=next[p]) if(key[p]==x) return vb[p];
		return -1;
	}
}Hash;
int p,a,b,sz;
inline int fpow(int x,int k){
	register int res=1;
	while(k){
		if(k&1) res=1ll*res*x%p;
		x=1ll*x*x%p;
		k>>=1;
	}
	return res;
}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("BSGS.in","r",stdin);
	freopen("BSGS.out","w",stdout);
#endif
	scanf("%d %d %d",&p,&a,&b);//a^x=b (mod p)
	sz=ceil(sqrt(p-1));//向上取整,保證不會漏解
	register int ans=-1;
	if(b==1) return puts("0"),0;
	for(register int i=0;i<sz;++i) Hash.insert(b,i),b=1ll*b*a%p;
	a=fpow(a,sz);
	register int res=a;
	for(register int i=1;i<=sz;++i){
		register int q=Hash.find(res);
		if(q!=-1) {ans=i*sz-q;break;}
		res=1ll*res*a%p;
	}
	if(ans==-1) puts("no solution");
	else printf("%d\n",ans);
	return 0;
}

擴展BSGS:

然后討論a,p不是質數的情況
唯一的問題是我們不能直接把一個數給除過去,因為a,p不互質的話a是沒有逆元的
我們記\(gcd(a,p)=d\),根據同余的一個性質:

\(a*c \equiv b*c \ (mod \ p)\),\(gcd(c,p)=d\),則\(a\equiv b \ (mod\ \frac{p}{d})\)
於是我們可以通過這個性質不斷通過提取左邊的一個a去把右邊的p給弄成a,p是互質的情況,記錄一下我們提取了多少個,最后加入答案就行了,如果提取過程中\(b\)不能被\(d\)整除就無解

記得如果在提取過程中已經出解了就要判掉

代碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<queue>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N=1e5+1;
const int mod=1e6+3;
typedef long long ll;
inline int fpow(int x,int k,int MO){
	register int res=1;
	while(k){
		if(k&1) res=1ll*res*x%MO;
		x=1ll*x*x%MO;
		k>>=1;
	}
	return res;
}
namespace BSGS{
	struct hashlist{
		int key[N],next[N],head[mod];
		int cnt;int vb[N];
		void clear(){Set(key,0);Set(next,0);Set(head,0);Set(vb,0);cnt=0;}
		inline void insert(int x,int b){
			register int ret=x%mod,p;
			for(p=head[ret];p;p=next[p]){
				if(key[p]==x) return void(vb[p]=max(vb[p],b));
			}
			++cnt;vb[cnt]=b;key[cnt]=x;next[cnt]=head[ret];head[ret]=cnt;
			return;
		}
		inline int find(int x){
			register int ret=x%mod,p;
			for(p=head[ret];p;p=next[p]) if(key[p]==x) return vb[p];
			return -1;
		}
	}Hash;
	inline int solve(int p,int a,int b,int d,int k){
		Hash.clear();int sz=ceil(sqrt(p-1));register int ans=-1;
		for(register int i=0;i<sz;++i){Hash.insert(b,i);b=1ll*b*a%p;}
		a=fpow(a,sz,p);
		for(register int i=1;i<=sz;++i){
			d=1ll*d*a%p;
			register int q=Hash.find(d);
			if(q!=-1) {
				ans=i*sz-q+k;break;
			}
		}
		return ans;
	}
}
int gcd(int a,int b){return (b? gcd(b,a%b):a);}
inline void EXBSGS(int p,int a,int b){
	a%=p;b%=p;
	if(b==1) return void(puts("0"));
	register int g=gcd(a,p),d=1,k=0;
	while(((g=gcd(a,p))!=1)){//不互質就繼續消
		if(b%g) return void(puts("no solution"));//gcd不整除 b 則無解
		++k;b/=g,p/=g;d=1ll*d*(a/g)%p;//記錄提取數量
		if(b==d) return void(printf("%d\n",k));//答案在消的過程中產生的情況
	}
	register int ans=BSGS::solve(p,a,b,d,k);
	if(ans==-1) return void(puts("no solution"));
	printf("%d\n",ans);
	return ;
}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("EXBSGS.in","r",stdin);
	freopen("EXBSGS.out","w",stdout);
#endif
	register int p,a,b;
	while(scanf("%d %d %d",&p,&a,&b)!=EOF) EXBSGS(p,a,b);
}

THE END

updated on 2018.8.16


免責聲明!

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



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