由於有春節,因此實際上寒假比暑假長
主要內容:原根 / 離散對數 / 二次剩餘ide
下面只討論奇素數 \(p\) 的原根,在不特別強調時,運算在模 \(p\) 意義下進行。函數
對於與 \(p\) 互質的常數 \(a\),在知足 \(a^x\equiv1\) 的全部正整數 \(x\) 中,最小的 \(x\) 稱爲 \(a\) 模 \(p\) 的階,記做 \(ord(a)\)。ui
Theorem 1this
當且僅當 $ord(a)\mid x$,有 $a^x\equiv 1$。spa
假設 $a^x\equiv 1$ 且 $a^y\equiv 1$,則有 $a^{x+y}\equiv 1$ 且 $a^{x-y}\equiv 1$。code
展轉相除法可得 $a^{(x,y)}\equiv1$。ip
$(x,y)\le \min\{x,y\}$,又有 $ord(a)$ 是知足 $a^x\equiv1$ 的最小 $x$,因此 $ord(d)\mid x$。ci
Theorem 2rem
若 $k\mid ord(a)$,則 $ord(a^k)=\frac{ord(a)}k$get
顯然有 $frac{ord(a)}k$ 是 $(a^k)^x\equiv 1$ 的解(代入方程、結合 $ord(a)$ 定義便可證實),只需證實 $\frac{ord(a)}k$ 是最小解。
假如存在 $x_0< \frac{ord(a)}k$ 也是方程的解,則 $kx_0$ 是 $a^x\equiv1$ 的解;而 $kx_0< ord(a)$,與 $ord(a)$ 定義矛盾。
根據 Theorem 1 有推論 \(ord(a)\mid (p-1)\),證實即費馬小定理。
若在模 \(p\) 意義下,\(x^0,x^1,\cdots,x^{\varphi(p)-1}\) 互不相同,也即 \(ord(x)=\varphi(p)\),則稱 \(x\) 是 \(p\) 的原根,記爲 \(\xi\)。
實際上只有形如 \(p^k,2p^k\) 的數以及 \(2,4\) 有原根(雖然我不會證)。
而根據原根的定義,模 \(p\) 意義下的任意元素均可以表示爲 \(\xi^k\)(原根的一個重要做用)。
Theorem 3
設 $\xi$ 是質數 $p$ 的一個原根,則 $\xi^k$ 是原根當且僅當 $(k,p-1)=1$。
因爲任意元素均可以用 \(\xi^k\) 表示。
假設 \(\xi^k\) 是 \(p\) 的原根,則必須 \(ord(\xi^k)=\varphi(p)=p-1\)。令 \(h=(k,p-1)\)
則有 \(ord(\xi^k)\mid\frac{p-1}h\)。
根據 Theorem 3,能夠知道 \(p\) 的原根恰有 \(\varphi(p-1)\) 個。
補充一個講課的dalao不會證因此我也不會證的定理:
Theorem 4(二次互反律)
$p,q$ 均爲奇素數,則
$$\left(\frac pq\right)\cdot\left(\frac qp\right)=(-1)^{\frac{p-1}2\times \frac{q-1}2}$$
直接從小到大枚舉 awa
注意到 \(p\) 的原根有 \(\varphi(p-1)\) 個,其實仍是蠻多的,因而枚舉不了多少個就能夠找到一個最小的原根 \(\xi_0\)。
而後再枚舉 \((k,p-1)=1\) 的 \(k\),就能夠找出剩下的原根 \(\xi_0^k\) 了。(通常不會真的讓你求全部原根)
怎麼驗算數 \(x\) 是否是 \(p\) 的原根?
int nprm,nfc,nans; int prm[N],phi[N],vis[N],fc[N],ans[N]; bool ifrt[N]; //ifrt[i]=true: i有原根 void Init(){ //線性篩篩素數和歐拉函數 phi[1]=1; for(int i=2;i<N;i++){ if(!vis[i]) prm[++nprm]=i,phi[i]=i-1,vis[i]=i; for(int j=1;j<=nprm && prm[j]*i<N;j++){ vis[prm[j]*i]=prm[j]; if(i%prm[j]==0) {phi[i*prm[j]]=phi[i]*prm[j];break;} phi[i*prm[j]]=phi[i]*(prm[j]-1); } } //2,4,prime^k,2*prime^k 纔有原根 ifrt[2]=ifrt[4]=true; for(int i=2;i<=nprm;i++){ for(llong j=prm[i];j<N;j*=prm[i]) ifrt[j]=true; for(llong j=2*prm[i];j<N;j*=prm[i]) ifrt[j]=true; } } int GCD(cint a,cint b){return b? GCD(b,a%b):a;} int QPow(int a,int b,cint MOD){ int r=1; while(b){ if(b&1) r=1ll*r*a%MOD; a=1ll*a*a%MOD; b>>=1; } return r; } //把 p 的全部質因子存入 fc[1~nfc] 中 void PrimeDivide(int p){ int las=-1; nfc=0; while(p>1){ if(las!=vis[p]) fc[++nfc]=vis[p]; las=vis[p]; p/=vis[p]; } } bool Check(cint x,cint p){ //先驗算 x 是否知足 ord(x)|phi[p] if(QPow(x,phi[p],p)!=1) return false; //而後驗算 ord(x) 是不是 phi[p] for(int i=1;i<=nfc;i++) if(QPow(x,phi[p]/fc[i],p)==1) return false; return true; } int MinRoot(cint p){ //找最小的原根 PrimeDivide(phi[p]); for(int i=1;i<p;i++) if(Check(i,p)) return i; return 0; } void AllRoot(cint p){ nans=0; int per=MinRoot(p),prod=1; for(int i=1;i<=phi[p];i++){ //拓展出全部的原根 prod=(1ll*prod*per)%p; if(GCD(i,phi[p])==1) ans[++nans]=prod; } }
求出下面方程的全部非負整數解:
其中知足 \((a,p)=1\);亦可擴展至 \((a,p)\neq 1\)。
根據歐拉定理,當 \((a,p)=1\) 時,有 \(a^{\varphi(p)}\equiv1\)。記原方程的最小正整數解爲 \(x_0\),則原方程的解 \(x\) 能夠表示爲 \(x=x_0+k\varphi(p)\),因而只須要求 \(x< \varphi(p)\) 的解。
令 \(q=\lfloor\sqrt p\rfloor\),將原方程的解 \(x\) 分解爲 \(x=nq-m\),其中 \(0\le m< q\)。易知 \(n\) 的數量級與 \(q\) 同階。
原方程能夠寫爲:
因爲 \(n,m\) 都是 \(O(\sqrt p)\) 級別的,能夠先 \(O(\sqrt p)\) 將方程一邊的全部取值都存進哈希表,而後另外一邊 \(O(\sqrt p)\) 查詢哈希表中是否有對應的值。
例如,將 \(m=0,1,\cdots,q-1\) 時的所有 \(ba^m\) 存入哈希表,而後枚舉 \(n\),查找哈希表中是否存在 \(a^{nq}\)。
以前提到離散對數能夠擴展到 \((a,p)\neq 1\) 的狀況,下面進行推導。
同餘方程 \(a^x\equiv b\pmod p\) 有以下性質:
因而能夠轉化爲
方程左邊至關於帶了一個係數,注意到 \(a\) 仍有可能和 \(\frac pg\) 不互質,只須要一直將方程同除以 \((a,p)\),直到 \(a,p\) 互質便可。
記每一次方程總體除的數是 \(g_i\),\(G=\prod g_i\),最後方程的形式大概是
求解方法仍是和 BSGS 同樣,用哈希表暴力儲存方程一邊的全部取值。
注意到咱們並不能保證不存在 \(x< k\) 的解。在轉化過程當中咱們直接從 \(a^x\) 中提取出一個 \(a\) 而剩下 \(a^{x-1}\) 實際上是不嚴謹的,由於轉化時 \(a,p\) 不互質,則 \(a^{-1}\) 不存在。
因而還要特判一下 \(x<k\) 有沒有解。
/* Hs 是手寫的一個哈希表,做用至關於 map */ int GCD(int a,int b){return b? GCD(b,a%b):a;} int exBSGS(int vara,int varb,int varp){ int total=1,add=0,mul=1,tmp=0,nowp=varp; while((tmp=GCD(vara,nowp))!=1) nowp/=tmp,total*=tmp,mul=1ll*(vara/tmp)*mul%varp,add++; tmp=1; for(int i=0;i<add;i++,tmp=1ll*tmp*vara%varp) if(tmp==varb) return i; if(varb%total) return -1; varb/=total; //mul*vara^(x-add)=varb (mod nowp) //mul*vara^y=varb (mod nowp) int varq=int(ceil(sqrt(nowp))+0.5),per=1; Hs.Clear(); tmp=varb%nowp; for(int i=0;i<varq;i++,tmp=1ll*tmp*vara%nowp) Hs.Insert(tmp,i),per=1ll*per*vara%nowp; tmp=mul%nowp; for(int i=0,res;i<=varq;i++,tmp=1ll*tmp*per%nowp) if(~(res=Hs.Query(tmp)) && i*varq-res>=0) return i*varq-res+add; return -1; } //BSGS找最小的解 void BSGS(int varp,int varn){ varq=(int)(ceil(sqrt(varp))+0.5); int per=1; for(int i=0,tmp=varn;i<varq;i++,tmp=1ll*tmp*varb%varp) Hs.Insert(tmp,i),per=1ll*per*varb%varp; for(int i=0,tmp=1,res;i<=varq;i++,tmp=1ll*tmp*per%varp) if(~(res=Hs.Query(tmp)) && i*varq>=res){ printf("%d\n",i*varq-res); return; } printf("no solution\n"); }
求解 \(x^2\equiv b\pmod p\),\(p\) 是一個奇素數。
若方程有解,則 \(b\) 稱爲 \(p\) 的二次剩餘
Lemma 1
$$\left(\frac bp\right)\equiv b^{\frac{p-1}2}$$
簡單證實一下:
Lemma 2
$p$ 剛好有 $\frac{p-1}2$ 個二次剩餘。
證實以下:設 \(x,y\) 知足 \(x^2\equiv y^2\)(\(x< y\),\(x,y\in[1,p-1]\)),移項可得:
又有 \(p\) 是素數且 \(p\not\mid x-y\),則 \((p,x-y)=1\),進而可得 \(p\mid x+y\)。在 \([1,p-1]\) 中,這樣的 \((x,y)\) 與 \(x^2\equiv y^2\) 一一對應。
因此 \([1,p-1]\) 中恰有 \(\frac{p-1}2\) 個不一樣的 \(x^2\),即 \(\frac{p-1}2\) 個二次剩餘,剩下的 \(\frac{p-1}2\) 個數則是非二次剩餘。
因而有結論:\(p\) 的二次剩餘和非二次剩餘同樣多。
Lemma 3
$$(a+b)^p\equiv a^p+b^p\pmod p$$
直接二項式展開,\(a^ib^{p-i}\) 項的係數爲 \(C_p^i\)。
由於 \(p\) 是質數,因此當 \(i\neq 0,i\neq p\) 時,\(C_p^i=\frac{p!}{i!(p-i)!}\) 的分子是 \(p\) 的倍數而分母不是,因此 \(p\mid C_p^i\)。
取模後只剩下首尾兩項。
若是咱們知道 \(b\) 是 \(p\) 的二次剩餘,如何求解?
能夠先隨機出一個 \(a\) 使得 \(a^2-b\) 不是 \(n\) 的二次剩餘,用勒讓德函數檢驗便可。因爲 \(p\) 的二次剩餘和非二次剩餘是同樣多的,因此能夠較快地隨機出這個 \(a\)。
能夠知道不存在整數 \(x\) 使得 \(x^2=a^2-b\),因而咱們「擴展數域」,讓這樣的 \(x\) 存在——定義單位 \(\omega^2\equiv a^2-b\),相似於複數域,域中的全部數能夠表示爲 \(p+q\omega\) 的形式。
Theorem 5
$(a+\omega)^{\frac{p+1}2}$ 是原方程的一個根。
直接代入證實:
\(p\) 是奇素數,\(\omega^2\equiv a^2-b\),則
由於 \(a^2-b\) 是非二次剩餘,因此 \((a^2-b)^{\frac{p-1}2}\equiv-1\)。又由費馬小定理,\(a^p\equiv a\),因此
因此
因而 \(x_1=(a+\omega)^{\frac{p-1}2}\)。由 Lemma 2,另外一個根 \(x_2\) 知足 \(p\mid x_1+x_2\),則 \(x_2\equiv p-x_1\)。
由於 \(p\) 是奇素數,\(x_1,x_2\) 必定奇偶性不一樣,則有兩個不等根。
> Link 洛谷 P5491
/*Lucky_Glass*/ #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long llong; #define cint const int & int varp,conw; int Mul(cint a,cint b){return int(1ll*a*b%varp);} int Add(cint a,cint b){return a+b>=varp? a+b-varp:a+b;} int Sub(cint a,cint b){return a-b<0? a-b+varp:a-b;} int Pow(cint a,cint b){return b? Mul(Pow(Mul(a,a),b>>1),(b&1)? a:1):1;} struct COMPLEX{ int conr,coni; COMPLEX(){} COMPLEX(cint varr,cint vari):conr(varr),coni(vari){} inline friend COMPLEX operator *(const COMPLEX &A,const COMPLEX &B){ return COMPLEX( Add(Mul(A.conr,B.conr),Mul(Mul(A.coni,B.coni),conw)), Add(Mul(A.conr,B.coni),Mul(A.coni,B.conr)) ); } inline COMPLEX operator ^(int varb)const{ COMPLEX vara=(*this),res(1,0); while(varb){ if(varb&1) res=res*vara; vara=vara*vara; varb>>=1; } return res; } }; //p is an odd prime int Solve(int varn){ varn%=varp; if(Pow(varn,(varp-1)/2)==varp-1) return -1; int vara,varw; while(true){ vara=Mul(rand(),rand()); varw=(Sub(Mul(vara,vara),varn)); if(Pow(varw,(varp-1)/2)==varp-1) break; } conw=varw; return (COMPLEX(vara,1)^((varp+1)/2)).conr; } int main(){ int cas;scanf("%d",&cas); while(cas--){ int varn;scanf("%d%d",&varn,&varp); if(!varn){printf("0\n");continue;} int ans1=Solve(varn),ans2; if(~ans1){ ans2=varp-ans1; if(ans1>ans2) swap(ans1,ans2); printf("%d %d\n",ans1,ans2); } else printf("Hola!\n"); } return 0; }
> Linked 餘命3日少女-網易雲