一.阶与阶的性质.
首先我们回忆一下欧拉定理.
欧拉定理:对于
gcd(a,p)=1,有
aϕ(p)≡1(modp).
根据欧拉定理,若
gcd(a,p)=1,则必然有一个数
x满足
ax≡1(modp).
阶:对于一个满足
gcd(a,p)=1的离散对数方程
ax≡1(modp),我们称最小的正整数
x为
a对
p的阶,我们记为
ordpa.
当
x=ϕ(p)时,我们称
a为
p的原根.
阶有如下几个性质:
性质1:
ax≡1(modp)⇔ordpa∣x.
证明:
首先,若
ordpa∣x,设
x=k∗ordpa,则:
ax≡ak∗ordpa≡(aordp)k≡1(modp)
反过来,对于任意
x=k∗ordpa+r(0<r<ordpa),则:
ax≡ak∗ordpa+r≡ar(modp)
根据定义,
ordpa是使得
ax≡1(modp)的最小正整数,不存在更小的正整数满足此方程,故原定理得证.
证毕.
扫描二维码关注公众号,回复:
6163636 查看本文章
性质2:
ordpa∣ϕ(p).
利用欧拉定理与性质1即可得证.
性质3:
{a0,a1a2,...,aordpa}构成
p的简化剩余系.即
i≡j(modordpa)⇔ai≡aj(modp).
证明:
若
i≡j(modordpa),令
i=k∗ordpa+j,那么:
ai≡ak∗ordpa+j≡ajak∗ordpa≡aj(modp)
反之,若
ai≡aj(modp)且
i≥j,由于
gcd(aj,n)=1,显然:
ai≡aj≡ajai−j(modp)ai−j≡1
由性质1可得
ordpa∣i−j,即
i≡j(modordpa).
证毕.
性质4:令
t=ordpa且
u∈Z,那么有:
ordp(au)=gcd(t,u)t
证明:
令
g=gcd(t,u),t′=gt,u′=gu,显然有:
(au)t′≡au′t≡1(modp)
反过来,由于
gcd的极大性,显然
t′是最小的满足
(au)t′≡1(modp)的正整数.
证毕.
二.原根与原根的性质.
原根:若
gcd(r,p)=1且有
ordpr=ϕ(p)时,我们称
r为
p的原根.
关于原根有几个性质:
性质5:若
gcd(r,p)=1且
r是
p的一个原根,则
{r1,r2,r3,...,rϕ(p)}构成
p的简化剩余系.
利用性质3可证.
性质6:若
r是
p的原根,那么
ru是
p的原根当且仅当
gcd(u,ϕ(p))=1.
证明:
由性质4可得:
ordpru=gcd(u,ordpr)ordpr=gcd(u,ϕ(p))ϕ(p)
证毕.
性质7:若
p由原根,则它所有在模
p意义下的不同原根数量为
ϕ(ϕ(p))个.
证明:
若
r是
p的一个原根,根据性质3,则
{r1,r2,...rϕ(p)}为
p的一个简化剩余系.根据性质6,
ru是
p的原根当且仅当
gcd(u,ϕ(p))=1,所有满足的
u有
ϕ(ϕ(p))个.
证毕.
性质8:任意素数都有原根.
这个东西需要研究多项式同余理论,这里不再赘述.
性质9:一个正整数
n存在原根当且仅当
n=2,4,pt,2pt.其中
p为奇素数(不等于
2的素数)且
t为正整数.
这个东西需要研究大量初等数论知识,这里不再赘述.
三.求解阶与原根.
求解
ordna,可以通过性质2,
O(n
)求出
ϕ(n),然后枚举
ϕ(n)的因数,每次通过快速幂判定即可,时间复杂度
O(n
logn).
代码如下:
LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
LL power(LL a,LL k,LL p){
LL s=1;
for (;k;k>>=1,a=a*a%p)
if (k&1) s=s*a%p;
return s;
}
LL phi(LL n){
LL v=n,ans=n;
for (LL i=2;i*i<=n;++i)
if (v%i==0){
ans=ans/i*(i-1);
while (v%i==0) v/=i;
}
if (v>1) ans=ans/v*(v-1);
return ans;
}
LL get_ord(LL a,LL n){
if (gcd(a,n)^1||n<2) return -1;
LL v=phi(n),mn=v,t;
for (LL i=1;i*i<=v;++i)
if (v%i==0){
if (power(a,i,n)==1) mn=min(mn,i);
t=v/i;if (power(a,t,n)==1) mn=min(mn,t);
}
return mn;
}
求解
n的原根(一般是最小原根),首先要了解一个比较容易的性质.
性质10:设唯一分解
ϕ(n)=∏i=1kpici,那么
g是
n的原根当且仅当
gcd(g,n)=1且:
∀i,gpiϕ(n)≢1(modn)
很显然就不证了吧…
那么我们先把
n的所有素因子存下来.由于
n的最小原根
g一般较小,所以暴力枚举
g,每次大力判定是否满足条件即可.时间复杂度
O(n
+glog2n).
若需要判定是否有原根,则需要用到性质9.
代码如下:
LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
LL power(LL a,LL k,LL p){
LL s=1;
for (;k;k>>=1,a=a*a%p)
if (k&1) s=s*a%p;
return s;
}
LL get_proot(LL n){
if (n==2) return 1;
if (n==4) return 3;
LL v,phi=n;
v=n&1?n:n>>1;
if (v&1^1) return -1;
for (LL i=3;i*i<=n;++i)
if (v%i==0){
phi=phi/i*(i-1);
while (v%i==0) v/=i;
if (v>1) return -1;
}
if (v>1) phi=phi/v*(v-1);
if (n&1^1) phi>>=1;
v=phi;
for (LL i=2;i*i<=phi;++i)
if (v%i==0){
pr[++tp]=i;
while (v%i==0) v/=i;
}
if (v>1) pr[++tp]=v;
bool flag;
for (LL g=2;g<n;++g){
if (gcd(g,n)^1) continue;
flag=0;
for (int i=1;i<=tp;++i)
if (power(g,phi/pr[i],n)==1){flag=1;break;}
if (flag) continue;
return g;
}
}
当然还有一份仅仅用于求素数原根的代码:
LL power(LL a,LL k,LL p){
LL s=1;
for (;k;k>>=1,a=a*a%p)
if (k&1) s=s*a%p;
return s;
}
LL get_proot(LL n){
if (n==2) return 1;
LL phi=n-1,v=phi;
for (LL i=2;i*i<=phi;++i)
if (v%i==0){
pr[++tp]=i;
while (v%i==0) v/=i;
}
if (v>1) pr[++tp]=v;
bool flag;
for (LL g=2;g<n;++g){
flag=0;
for (int i=1;i<=tp;++i)
if (power(g,phi/pr[i],n)==1) {flag=1;break;}
if (flag) continue;
return g;
}
}
四.例题与代码.
题目(原根板子):51nod1135.
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=100;
LL p,pr[N+9],tp;
LL power(LL a,LL k,LL p){
LL s=1;
for (;k;k>>=1,a=a*a%p)
if (k&1) s=s*a%p;
return s;
}
LL get_proot(LL n){
if (n==2) return 1;
LL phi=n-1,v=phi;
for (LL i=2;i*i<=phi;++i)
if (v%i==0){
pr[++tp]=i;
while (v%i==0) v/=i;
}
if (v>1) pr[++tp]=v;
bool flag;
for (LL g=2;g<n;++g){
flag=0;
for (int i=1;i<=tp;++i)
if (power(g,phi/pr[i],n)==1) {flag=1;break;}
if (flag) continue;
return g;
}
}
Abigail into(){
scanf("%lld",&p);
}
Abigail outo(){
printf("%lld\n",get_proot(p));
}
int main(){
into();
outo();
return 0;
}