阶与原根学习笔记

V.阶与原根

实际上这部分内容在OI中应用很少,但它是一些重要思想以及算法的基础。

阶是在互质数 (a,m) 间的定义:满足 an1(modm) 的最小 n 被称作 am 的阶,记作 δm(a)

明显,在 a,m 不互质时,a 的无论多少次幂全都是 gcd(a,m) 的倍数,故不可能取到 1;而当互质时,依据扩展欧拉定理,必有 aφ(m)1(modm),但也不排除存在更小的 n

阶有什么性质呢?

性质1.a1,a2,,aδa(m)m 两两不同余。

如果两个数 ai,aj 同余的话(不妨设 i<j),则 ai×(aji1)0(modm),而 ai0(modm),故只有可能 aji10(modm),即 aji0(modm),而 ji 一定是 <δa(m) 的,故此时 δa(m) 就不是最小的满足 an1(modm)n 了,与前提矛盾。

性质2.若 an1(modm),则 δm(a)|n

f(n)=anmodm 这个函数,依照性质1,其最短循环节是 δm(a)。故所有模 m1 的数一定只存在于 δm(a) 倍数的位置。


一个数 g 被称作 m 的原根,当且仅当 δm(g)=φ(m)

显然,其前提条件是 gcd(g,m)=1,不然 δm(g) 不存在。

原根判定定理:gm 的原根,当且仅当对于一切 φ(m) 的质因数 p,都有 gφ(m)/p1(modm)

原根个数定理:若 m 有原根,则原根个数是 φ(φ(m))

原根存在定理:m 有原根,当且仅当 m{2,4,pα,2pα},其中 p 是奇质数,αN+

最小原根级别定理:若 m 有原根,则最小原根是 m4 级别的。

什么?你问证明?但是考试也不考证明啊(摊手)

原根寻找方法:暴力找到最小原根 g,则对于所有与 φ(m) 互质的 xgx 都是 m 的原根。

时间复杂度 O(logm(m4+φ(φ(m)))+φ(m))

总结步骤:

  1. 判断 m 是否 {2,4,pα,2pα},不是则直接返回。
  2. 预处理 φ(m) 所有质因数。
  3. 从小到大枚举与 m 互质的 i,并依次用快速幂检验 φ(m) 所有质因数。
  4. 找到最小原根,枚举所有与 φ(m) 互质的数,求快速幂即可。

V.I.【模板】原根

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1000000;
int T,n,d,pri[1001000],phi[1001000],mnp[1001000];
int ksm(int x,int y){
	int z=1;
	for(;y;y>>=1,x=1ll*x*x%n)if(y&1)z=1ll*z*x%n;
	return z;
}
void sieve(){
	for(int i=2;i<=N;i++){
		if(!pri[i])pri[++pri[0]]=i,phi[i]=i-1,mnp[i]=i;
		for(int j=1;j<=pri[0]&&i*pri[j]<=N;j++){
			pri[i*pri[j]]=true,mnp[i*pri[j]]=pri[j];
			if(i%pri[j])phi[i*pri[j]]=phi[i]*phi[pri[j]];
			else{phi[i*pri[j]]=phi[i]*pri[j];break;}
		}
	}
}
vector<int>factorize(int ip){
	vector<int>ret;
	while(ip!=1)ret.push_back(mnp[ip]),ip/=mnp[ip];
	sort(ret.begin(),ret.end()),ret.resize(unique(ret.begin(),ret.end())-ret.begin());
	return ret;
}
bool checkrootexist(int ip){
	if(n==2||n==4)return true;
	vector<int>v=factorize(ip);
	if(v.size()==1&&v[0]!=2)return true;
	if(ip&1)return false;
	v=factorize(ip/2);
	return v.size()==1&&v[0]!=2;
}
vector<int>fp;
bool checkisroot(int ip){
	for(auto i:fp)if(ksm(ip,phi[n]/i)==1)return false;
	return true;
}
vector<int>res;
void findallroots(int ip){
	for(int i=1;i<=phi[n];i++)if(__gcd(i,phi[n])==1)res.push_back(ksm(ip,i));
	sort(res.begin(),res.end());
} 
int main(){
	sieve();
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&d),res.clear();
		if(!checkrootexist(n)){puts("0\n");continue;}
		fp=factorize(phi[n]);
		for(int i=1;;i++)if(__gcd(i,n)==1&&checkisroot(i)){findallroots(i);break;}
		printf("%d\n",res.size());
		for(int i=d-1;i<res.size();i+=d)printf("%d ",res[i]);puts("");
	}
	return 0;
}

posted @   Troverld  阅读(308)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示