原根 学习笔记

假设 gcd(a,p)=1,如果 ax1(modp),那么最小的 x 称之为 a 在模 p 意义下的阶,记作 δp(a)

在抽象代数中,这里的“阶”就是模 p 缩剩余系关于乘法形成的群中,元素 a 的阶。记号 δ 表示阶也只用于这个特殊的群。

性质

  1. a1,a2,,aδp(a)p 两两不同余。
  2. 如果 an1(modp),那么 δp(a)n。同时有 anam(modp) 那么 nm(modδp(a))
  3. δp(ab)=deltap(a)deltap(b) 的充要条件是 gcd(deltap(a),deltap(b))=1
  4. δp(ak)=δp(a)gcd(δp(a),k)(*)

以上均可以使用定义或者反证法证明。

原根

假设 gcd(a,p)=1,如果 δp(a)=φ(p),那么 a 是模 p 意义下的原根。
也就是说,如果 a 是模 p 的原根,那么 a1,a2,,aφ(p)p 两两不同。

判定定理

对于 gcd(a,p)=1,如果 φ(p) 的所有质因子 q 都有 aφ(p)/q1(modp),那么 a 是模 p 意义下的原根。
必要性显然,充分性可以反证。

数量

如果 p 存在一个原根 g,那么

δp(gk)=δp(g)gcd(δp(g),k)=φ(p)gcd(varphi(g),k)

如果 gcd(φ(p),k)=1,那么 gkmodp 就是 p 的原根。
所以 p 如果有原根,那么就有 φ(φ(p)) 个。

原根存在定理

p 存在原根当且仅当 p=2,4,qα,2qα,其中 q 为质数,αN
证明

最小原根

可以证明 p 如果有原根,那么最小原根是小于 p0.25 级别的。不过其实当 p 增大,最小原根不是多项式级别,所以我们可以直接暴力找最小原根。

模板题

Link
找一个数的所有原根。
其实只需要找到最小原根 p,然后只要满足 gcd(φ(p),k)=1,那么 gkmodp 就是原根。

#include<cstdio>
#include<algorithm>
#define db double
#define gc getchar
#define pc putchar
#define U unsigned
#define ll long long
#define ld long double
#define ull unsigned long long
#define Tp template<typename _T>
#define Me(a,b) memset(a,b,sizeof(a))
Tp _T mabs(_T a){ return a>0?a:-a; }
Tp _T mmax(_T a,_T b){ return a>b?a:b; }
Tp _T mmin(_T a,_T b){ return a<b?a:b; }
Tp void mswap(_T &a,_T &b){ _T tmp=a; a=b; b=tmp; return; }
Tp void print(_T x){ if(x<0) pc('-'),x=-x; if(x>9) print(x/10); pc((x%10)+48); return; }
#define EPS (1e-7)
#define INF (0x7fffffff)
#define LL_INF (0x7fffffffffffffff)
#define N 1000000
#define maxn 1000039
#define maxm
#define MOD
#define Type int
#ifndef ONLINE_JUDGE
//#define debug
#endif
using namespace std;
Type read(){
char c=gc(); Type s=0; int flag=0;
while((c<'0'||c>'9')&&c!='-') c=gc(); if(c=='-') c=gc(),flag=1;
while('0'<=c&&c<='9'){ s=(s<<1)+(s<<3)+(c^48); c=gc(); }
if(flag) return -s; return s;
}
int isp[maxn],pr[maxn],phi[maxn],minx[maxn],cnt;
void init(){
int i,j; for(i=2;i<=N;i++) minx[i]=INF;
for(i=2;i<=N;i++){
if(!isp[i]) pr[++cnt]=i,phi[i]=i-1,minx[i]=i;
for(j=1;j<=cnt&&i*pr[j]<=N;j++){
isp[i*pr[j]]=1; minx[i*pr[j]]=mmin(minx[i*pr[j]],mmin(pr[j],minx[i]));
if(i%pr[j]==0){ phi[i*pr[j]]=phi[i]*pr[j]; break; }
phi[i*pr[j]]=phi[i]*(pr[j]-1);
}
} phi[1]=1,isp[1]=1; return;
}
int n,d,ans[maxn],num;
int check(int x){
if(x==2||x==4) return 0;
if(x%2==0) x/=2; if(x%2==0) return 1;
int i=minx[x]; while(x%i==0) x/=i;
return x!=1;
}
int gcd(int x,int y){ if(!x||!y) return x|y; return gcd(y,x%y); }
int fastpow(int x,int y){
int res=1,tmp=x;
while(y){
if(y&1) res=1ll*res*tmp%n;
y>>=1,tmp=1ll*tmp*tmp%n;
} return res;
}
int js(int x){
if(gcd(x,n)!=1) return 0;
int cur,tmp,las; cur=tmp=phi[n],las=-1;
while(cur!=1){
while(cur!=1&&minx[cur]==las) cur/=minx[cur];
if(cur==1) return 1;
if(fastpow(x,tmp/minx[cur])==1) return 0;
las=minx[cur]; cur/=minx[cur];
} return 1;
}
void work(){
n=read(); d=read(); if(check(n)){ pc('0'),pc('\n'),pc('\n'); return; }
int i,g; num=0; for(i=1;i<n;i++) if(js(i)){ g=i; break; }
for(i=1;i<=phi[n];i++) if(gcd(i,phi[n])==1) ans[++num]=fastpow(g,i);
sort(ans+1,ans+num+1); print(num),pc('\n');
for(i=d;i<=num;i+=d) print(ans[i]),pc(' '); pc('\n'); return;
}
int main(){
freopen("1.in","r",stdin);
//freopen(".out","w",stdout);
init(); int T=read(); while(T--) work(); return 0;
}
posted @   jiangtaizhe001  阅读(51)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示