数论——阶与原根
# 整数的阶
根据欧拉定理aφ(n) ≡ 1 (mod n),其中a与n互质,则至少存在一个x使得ax ≡ 1 (mod n)
阶的定义:
- a和n互质,使得 ax ≡ 1 (mod n) 成立的最小的正整数x就是a模n的阶,记做ordna,若ordna=x,ordnat=x/gcd(x,t)
性质:
- 若a与n是互质的整数,且n > 0,那么正整数x是同余式ax ≡ 1 (mod n)解的充要条件是ordna | x,易知ordna | φ(n)
# 原根
定义:
- 若 a 与 n 是互质的整数且n>0,0 < a < n,那么当 ordna = φ(n),称a为模n的原根。
性质:
- 每个素数都有原根,只是整数不一定有原根
- 当正整数n有原根时,有φ(φ(n))个原根
- 模n有原根的充要条件是n=2,4,pk,2pk,其中p是奇素数,k为任意正整数
- 若a为模n的原根,则ad为模n的原根的充要条件是gcd(d,φ(n))=1
- a0,a1,a2,......,aordna -1 模n 两不同余,所以当a是模n的原根时, a0,a1,a2,......,aordna -1构成模n的简化剩余系列
# 求原根
求模 n 原根的方法:对φ(n)分解质因数,即φ(n)=p1c1 · p2c2 · ....... · pncn
若恒有
aφ(n)/pi ≠ 1(mod n) i ∈ [1,n]
暴力从2到n枚举即可,原根的大小很小,一般不会超过300
对φ(n)的所有质因子验证上式,如果都满足就是原根。
# 模版
1 int primes[N+10],cnt; 2 ll euler[N]; 3 bool st[N+10]; 4 vector<ll>ans; 5 vector<int> factors;//质因子集合,只有质数,没有指数 6 void get_primes(){ 7 st[1] = 1; 8 euler[1] = 1; 9 for (int i = 2; i <= N; i ++ ) { 10 if (!st[i]) { 11 primes[cnt++] = i; 12 euler[i] = i - 1; 13 } 14 for (int j = 0; primes[j] <= N / i; j++) { 15 int t = primes[j] * i; 16 st[t] = true; 17 if (i % primes[j] == 0) { 18 euler[t] = euler[i] * primes[j]; 19 break; 20 } 21 euler[t] = euler[i] * (primes[j] - 1); 22 } 23 } 24 } 25 int gcd(int a,int b){return b?gcd(b,a%b):a;} 26 int qmi(ll a,ll k,ll p){ 27 ll res=1; 28 while(k){ 29 if(k&1) res=res*a%p; 30 a=a*a%p; 31 k>>=1; 32 } 33 return res; 34 } 35 void get_factors(int n) 36 { 37 factors.clear(); 38 for (int i = 2; i * i <= n; ++i) 39 { 40 if (n % i) continue; 41 factors.push_back(i); 42 do n /= i; 43 while (n % i == 0); 44 } 45 if (n > 1) factors.push_back(n); 46 } 47 //判断原根是否存在 48 bool judge(int n){ 49 // 模n有原根的充要条件是n=2,4,p^n,2p^n,其中p是奇素数,n为任意正整数 50 if(n%2==0) n/=2; 51 if(!st[n]) return true;//奇素数,有原根 52 for(int i=3;i*i<=n;i++){//偶素数只有2,从3开始即可 53 if(n%i==0){//遇到质因数除尽 54 while(n%i==0) n/=i; 55 return n==1;//能是有奇素数构成 56 } 57 } 58 return false; 59 } 60 int main(){ 61 long long n; 62 cin>>n; 63 ans.clear(); 64 if(n==2){ puts("1");continue;} 65 if(n==4){puts("3");continue;} 66 if(!judge(n)) {puts("-1");continue;}//不存在原根 67 int phi=euler[n]; 68 int aa=-1;//存最小的原根 69 get_factors(phi); 70 for(int a=2;a<n;a++){//求出一个最小的原根 71 bool flag=true; 72 aa=a; 73 if(qmi(a,phi,n)!=1) continue;//原根定义进行判断剪枝 74 for(int i=0;i<factors.size();i++){//枚举phi(n)的质因子 75 if(qmi(a,phi/factors[i],n)==1){//即小于phi(n)/p[i]也满足等式,不是原根 76 flag=false; 77 break; 78 } 79 } 80 if(flag){ 81 ans.pb(a);break;//a就是最小的原根 82 } 83 } 84 if(ans.size()==0) puts("-1"); 85 else { 86 for(int i=2;i<=phi;i++){//求所有的原根 87 if(gcd(i,phi)==1) ans.pb(qmi(aa,i,n)); 88 } 89 sort(ans.begin(),ans.end()); 90 unique(ans.begin(),ans.end()); 91 for(int i=0;i<ans.size();i++){ 92 printf("%lld%c",ans[i],(i==ans.size()-1)?'\n':' '); 93 } 94 } 95 }