定义

(a,m)=1(a,m)=1,满足 an=1(modm)a^n = 1 \pmod m 的最小的 nn 称为 aamm 的阶,记作 δma\delta_m a

性质

  1. a,a2,,aδmaa,a^2,\ldots,a^{\delta_m a}mm 两两不同。
  2. an1(modm)    δmana^n \equiv 1 \pmod m \iff \delta_m a \mid n
  3. apaq(modm)    pq(modδma)a^p\equiv a^q \pmod m \iff p \equiv q \pmod {\delta_m a}
  4. δm(ab)=δmaδmb    (δma,δmb)=1\delta_m (ab) = \delta_m a \delta_m b \iff (\delta_m a,\delta_m b)=1
  5. δm(ak)=δma(δma,k)\delta _m(a^k) = \dfrac{\delta_m a}{(\delta_m a,k)}
  6. k,ak=b    δmbδma\exists k, a^k = b \iff \delta_m b \mid \delta_m a。(可用阶性质 5 证明)

求法

如果求 aa 的阶,初始令阶 xxφ(m)\varphi(m),对 φ(m)\varphi(m) 的每一个质因数,如果除了它之后仍然有 ax1(modm)a^x \equiv 1 \pmod m,则不断除掉这个质因数,最后得到的就是它的阶。

复杂度 O(m+log2m)O(\sqrt m +\log^2 m)。(预处理质因数 m\sqrt m,有 logm\log m 个质因数,每次快速幂判断复杂度为 logm\log m).

参考代码:

若已知:

1
2
3
4
5
6
7
8
9
10
11
12
ll ksm(ll a,ll b,ll p){
ll r = 1;
while(b){
if(b&1)r=(__int128_t)r*a%p;
a=(__int128_t)a*a%p;
b>>=1;
}
return r;
}

ll a,p;
vector<ll> fp; // phi(p) 的因数

ord 就是 a 的阶。

1
2
3
4
ll ord = p-1;
for(auto f:fp){
while(ord%f==0 && ksm(a,ord/f,p)==1)ord/=f;
}

原根

定义

δm(g)=φ(m)\delta_m(g) = \varphi(m),称 gg 为模 mm 的原根。

性质

  1. mm 为质数时,gimodm(0<i<m)g^i \bmod m (0<i<m) 的值互不相同,恰为 1,2,,m11,2,\ldots,m-1。(阶性质 1)
  2. (原根判定定理)g 是 m的原根    pφ(m),gφ(m)g≢1(modm) 且 (g,m)=1g \text{ 是 } m \text{的原根} \iff \forall p \mid \varphi(m),g^{\frac{\varphi(m)}{g}}\not \equiv 1 \pmod m \text{ 且 } (g,m)=1
  3. (原根个数定理)若 ggmm 的一个原根,则所有原根可以表示为 gkg^k,其中 (k,φ(m))=1(k,\varphi(m))=1。故原根个数有 φ(φ(m))\varphi(\varphi(m))个。
  4. (原根存在定理)mm 存在原根当且仅当 m=2,4m=2,4m=pα,2pαm=p^\alpha, 2p^\alpha,其中
    pp 为质数,αN\alpha\in \N^*
  5. 若一个数 mm 存在原根,其最小原根 g=O(m1/4)g=O(m^{1/4})

求法

  1. 判断原根是否存在;O(n)O\left(\sqrt n\right) (或预处理 O(n)O(n)
  2. 若存在,从 11 开始枚举与 nn 互质的数,找到最小原根;O(n0.25log2n)O\left(n^{0.25} \log^2 n\right)
  3. 找出所有与 φ(n)\varphi(n) 互质的 kk,求出所有原根。O(φ(n)logφ(n))O(\varphi(n)\log\varphi(n))
  4. (对所有原根排序. O(φ(φ(n))logφ(φ(n)))O(\varphi(\varphi(n))\log\varphi(\varphi(n)))

参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include<bits/stdc++.h>
using namespace std;

using ll=long long;
inline int read(){int f=1,x=0;char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=x*10+c-'0';c=getchar();}return x*f;}

const int N = 1e6+5;
bool vis[N];
int pri[N],cnt,phi[N];
bool have[N];

int ksm(int a,int b,int p){
int r = 1;
while(b){
if(b&1)r=(ll)r*a%p;
a=(ll)a*a%p;
b>>=1;
}
return r;
}

int main(){
// 线性筛预处理出素数和欧拉函数
phi[1]=1;
for(int i=2;i<N;i++){
if(!vis[i])pri[cnt++]=i,phi[i]=i-1;
for(int j=0;j<cnt&&i*pri[j]<N;j++){
vis[i*pri[j]]=1;
if(i%pri[j]==0){
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
}


have[2]=have[4]=1; // 2,4
for(int i=1;i<cnt;i++){
for(ll j=pri[i];j<N;j*=pri[i])have[j]=1; // p^a
for(ll j=2*pri[i];j<N;j*=pri[i])have[j]=1; // 2p^a
}

int T=read();
while(T--){
int n=read(),d=read();
if(!have[n]){ // 没有原根
puts("0\n");
continue;
}

vector<int> fac; // 求出φ(n)的质因数
int P = phi[n];
for(int i=2;i*i<=P;i++){
if(P%i==0){
fac.push_back(i);
while(P%i==0)P/=i;
}
}
if(P!=1)fac.push_back(P);
vector<int> g;
for(int i=1;;i++){
if(__gcd(n,i)!=1)continue; // i 与 n 互质
bool ok = 1;
for(auto p:fac)
if(ksm(i,phi[n]/p,n)==1){ok=0;break;}

if(ok){
for(int k=1;k<=phi[n];k++)
// 找到所有原根
if(__gcd(k,phi[n])==1)g.push_back(ksm(i,k,n));
break;
}
}

sort(g.begin(),g.end());
assert(g.size()==phi[phi[n]]);
printf("%d\n",(int)g.size());
for(int i=d-1;i<g.size();i+=d)printf("%d ",g[i]);
puts("");
}

return 0;
}

离散对数

定义

设有原根的正整数 mm,设其一个原根为 gg
对于 (a,m)=1(a,m)=1 的整数 aa 恰有一个 kk 使 gka(modm)g^k\equiv a\pmod m,记 k=indgak=\operatorname{ind} _g a

性质

  1. indg1=0\operatorname{ind} _g 1 = 0
  2. indgg=1\operatorname{ind} _g g = 1
  3. ind(ab)=inda+indb(modφ(m))\operatorname{ind} (ab) = \operatorname{ind} a + \operatorname{ind} b \pmod {\varphi(m)}indanninda(modφ(m))\operatorname{ind} a^n \equiv n \operatorname{ind} a \pmod {\varphi(m)}
  4. indg1aindg1g2indg2a(modφ(m))\operatorname{ind} _{g_1} a \equiv \operatorname{ind} _{g_1} {g_2} \operatorname{ind} _ {g_2} a \pmod {\varphi(m)}
  5. ab(modm)    inda=indba\equiv b \pmod m \iff \operatorname{ind} a = \operatorname{ind} b

注意:在取离散对数之后,取模变为 φ(m)\varphi(m),而不是原来的 mm

求法

BSGS 算法:在 O(m)O(\sqrt m) 的时间内求 ax=b(modm)a^x=b\pmod m 的解。要求 (a,m)=1(a,m)=1

t=mt=\lceil\sqrt m\rceil。不妨设解 x=itjx=it-j。(0jt0\le j \le t

aitjb(modm)a^{it-j} \equiv b \pmod m,即 aitbaj(modm)a^{it}\equiv ba^j \pmod m

求出 i,ji,j 后就能得到答案。(互质的原因是我们推导用到了逆元)

参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<bits/stdc++.h>
using ll=long long;
using namespace std;
ll ksm(ll a,ll b,ll p){
ll r=1;
while(b){
if(b&1)r=r*a%p;
a=a*a%p;
b>>=1;
}
return r;
}
int main(){
ll p,a,b;
scanf("%lld%lld%lld",&p,&a,&b);
ll t=ceil(sqrt(p));
map<ll,int>vis;
for(ll i=0,now=b;i<=t;i++,now=now*a%p)vis[now]=i;
ll at = ksm(a,t,p);
for(ll i=1,now=at;i<=t;i++,now=now*at%p){
if(vis.count(now)){
printf("%lld\n",i*t-vis[now]);
return 0;
}
}
puts("no solution");
return 0;
}

习题

P6091 【模板】原根

P3846 [TJOI2007] 可爱的质数/【模板】BSGS

[ABC335G] Discrete Logarithm Problems 参考代码