原根(知识学习+板子总结+例题+应用)

思路来源

https://baike.baidu.com/item/%E5%8E%9F%E6%A0%B9/8103534?fr=aladdin

https://blog.csdn.net/zoro_n/article/details/78200742

https://www.cnblogs.com/Dance-Of-Faith/p/9905786.html

《数学的思维方式与创新》 丘维声著 北京大学出版社 2011年3月第一版

 

看到的一些和原根相关的比较好玩的东西

http://www.sohu.com/a/226489349_100125672

http://www.sohu.com/a/214094143_815436

 

概念

提示:如果下述出现一些符号不明白,可下翻到参考概念及预备知识部分

 

设m是正整数,a是整数,若a模m的阶等于φ(m)(即|a|==φ(m)),则称a为模m的一个原根。

 

*群的元素的阶

 

性质

 

①如果正整数(a,m) = 1和正整数 d 满足a^{d}≡1(mod m),则 d 整除 φ(m)。

因此|a|整除φ(m)。当a= 3时,我们仅需要验证 3 的 1 、2、3 和 6 次方模 7 的余数即可。

证明:

显然,由欧拉定理,a^{\varphi (m)}==1(mod m),结合下述命题2得证

从这个思路出发,这为寻找模m意义下的最小原根提供了方法论,

至少可以帮我们快速判定一个数不是原根,

 

性质1的方法论:

如果对于所有\varphi (m)的素因子prime[i],a^{\tfrac{\varphi (m)}{prime[i]}}模m均不为1,则|a|==φ(m),a为原根

证明:

上述证明引自博客https://www.cnblogs.com/Dance-Of-Faith/p/9905786.html

一点小说明,对于引理2,我们可以递归进行这个操作,a^{j}\equiv 1a^{i-j}\equiv 1,从而继续往小求,直至a^{(i,j)}\equiv 1

由于g是\varphi的一个约数,g<\varphi,则g至少比\varphi少一个素因子p_{i},那么\tfrac{\varphi}{p_{i}}一定是g的倍数,则得证

 

a^{0},……,a^{|a|-1}模 m 两两不同余,因此当a是模m的原根时,a^{0},……,a^{|a|-1}构成模 m 的简化剩余系。

简化剩余系,1到m-1中所有与m互素的数的集合。

a^{0},……,a^{|a|-1}这些数只能是可逆元,因此是可逆元的集合,不可能出现零因子,故均与m互素。

 

③模m有原根的充要条件是m= 1,2,4,p^{n},2p^{n},其中p是奇质数,n是任意正整数。

常用:2的原根是1;4的原根是3;998244353的原根是3;1e9+7的原根是5

该证明由高斯在1801年完成,菜鸡表示不会证QAQ

 

④对正整数(a,m) = 1,如果 a 是模 m 的原根,那么 a 是整数模n乘法群(即加法群 Z/mZ的可逆元,也就是所有与 m 互素的正整数构成的等价类构成的乘法群)Zn的一个生成元。由于Zn有 φ(m)个元素,而它的生成元的个数就是它的可逆元个数,即 φ(φ(m))个,因此当模m有原根时,它有φ(φ(m))个原根。

百度百科证明实在是看不懂,就是φ(φ(m))让我找到的这本书,更为详尽的证明

证明:

其实就是群论拉格朗日定理的一个结论,然而不写成这个命题3的形式我还是证不出来

*循环群与生成元

由群|Z_{m}^{*}|=\varphi(m),若存在原根a,则|a^{k}|=\tfrac{\varphi(m)}{gcd(\varphi (m),k))}

只需令gcd(\varphi(m),k)==1,即有|a^{k}|=\varphi(m)

|Z_{m}^{*}|=\varphi(m)知,a^{k}为群Z_{m}^{*}的生成元,

k的个数即为Z_{m}^{*}的生成元的个数,

也为满足gcd(\varphi(m),k)==1即与\varphi(m)互质的数的个数,

这显然就是\varphi(\varphi(m)),即Z_{m}^{*}可逆元的个数,证毕

故原根个数为0或\varphi(\varphi(m))

 

一个数的最小原根的大小是O(n^{\tfrac{1}{4}})的

详见百度百科最小正原根问题,由王元院士于1959年给出证明

 

⑥一个数n的全体原根乘积mod n==1

 

⑦一个数n的全体原根总和mod n==莫比乌斯函数μ(n-1)

 

⑧找到最小原根a后,所有与\varphi(n)互素的数k,a^{k}(mod n)均为原根

其实求模m剩余循环群Z_{m}^{*}的所有生成元的过程,就是求m的原根的过程,

这二者是等价的,原根个数\varphi(\varphi(5))=\varphi(4)=2,即有两个生成元

 

例题

①poj1284 Primitive Roots 求奇素数p的原根个数

根据性质4,显然答案为\varphi(\varphi(p))

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=(1<<16)+10;
bool ok[maxn];
int prime[maxn],phi[maxn],cnt,n;
void sieve()
{
        phi[1]=1;
	for(ll i=2;i<maxn;++i)
	{
		if(!ok[i])
		{
			prime[cnt++]=i;
			phi[i]=i-1;
		}
		for(int j=0;j<cnt;++j)
		{
			if(i*prime[j]>=maxn)break;
			ok[i*prime[j]]=1;
			if(i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];//prime[j]是i的因子 prime[j]的素因子项包含在i的素因子项里
				break; 
			}
			else phi[i*prime[j]]=phi[i]*(prime[j]-1);//prime[j]与i互质 phi[i*prime[j]=phi[i]*phi[prime[j]]
		}
	}
}
int main()
{
	sieve();
	while(~scanf("%d",&n))
	{
		printf("%d\n",phi[phi[n]]);
	}
	return 0;
}

 

②hdu4992 Primitive Roots 求一个数n的所有原根(2<=n<1e6)

先根据性质3判是否存在原根,

若存在,再根据性质1的方法论从a==2开始往上找最小原根a,

找到原根a后,根据性质8,输出这些原根即可

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<cmath> 
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
typedef long long ll;
bool ok[maxn];
vector<ll>cal,rt;
ll p,phi[maxn],prime[maxn],cnt;
ll modpow(ll x,ll n,ll mod)
{
	if(!n)return 1;
	ll p=modpow(x,n/2,mod),q=p*p%mod;
	if(n&1)q=q*x%mod;
	return q;
}
void sieve()
{ 
    phi[1]=1;
	for(ll i=2;i<maxn;++i)
	{
		if(!ok[i])
		{
			prime[cnt++]=i;
			phi[i]=i-1;
		}
		for(int j=0;j<cnt;++j)
		{
			if(i*prime[j]>=maxn)break;
			ok[i*prime[j]]=1;
			if(i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];//prime[j]是i的因子 prime[j]的素因子项包含在i的素因子项里
				break; 
			}
			else phi[i*prime[j]]=phi[i]*(prime[j]-1);//prime[j]与i互质 phi[i*prime[j]=phi[i]*phi[prime[j]]
		}
	}
}
vector<ll>divisor(ll p)
{
	vector<ll>tmp;
	for(int i=0;i<cnt;++i)
	{
		if(p%prime[i]==0)
		{
			tmp.push_back(prime[i]);
			while(p%prime[i]==0)
			p/=prime[i];
		}
	}
	if(p>1)tmp.push_back(p);
	return tmp;
}
bool judge(ll p)
{
	if(p%2==0)p/=2;
	if(!ok[p])return 1; 
	if(p%2==0)return 0;
	for(int i=1;i<cnt;++i)//得找奇素数 i=1很关键 
	{
		if(p%prime[i]==0)
		{
			while(p%prime[i]==0)
			p/=prime[i];
			return p==1;
		}
	}
	return 0;
}
vector<ll>solve(ll p)
{
	vector<ll>t; 
	if(p==2)
	{
		t.push_back(1);
		return t;
	}
	if(p==4)
	{
		t.push_back(3);
		return t;
	}
	if(!judge(p))
	{
		t.push_back(-1);
		return t;
	}
	ll root,now=1;//phi(p)
	cal=divisor(phi[p]);//phi(p)质因子 
	int len=cal.size();
	for(ll a=2;a<p;++a)
	{
	    if(modpow(a,phi[p],p)!=1)continue;
	    bool flag=1;
		for(int i=0;i<len&&flag;++i)
		{
			if(modpow(a,phi[p]/cal[i],p)==1)flag=0;
		}
		if(flag)
		{
			root=a;
			break;
		}
	}
	for(ll i=1;i<phi[p];++i)
	{
		now=(now*root)%p;
		if(__gcd(i,phi[p])==1)t.push_back(now);//algorithm库函数 
	}
	sort(t.begin(),t.end());
	ll sz=unique(t.begin(),t.end())-t.begin();//可能有重复 如26 
	t.resize(sz);
	return t;
}
int main()
{
	sieve();
	while(~scanf("%lld",&p))
	{
	 rt=solve(p);
	 int sz=rt.size();
	 for(int i=0;i<sz;++i)
	 printf("%lld%c",rt[i],i==sz-1?'\n':' ');
    }
	return 0;
}

 

③51nod1135 求最小原根(保证输入是一个3<=p<=1e9的奇素数)

和上面的代码大同小异,由于题意中p一定是素数,直接把phi[p]的地方都换成p-1即可

然后找到root之后就返回即可,不用找剩下的原根了

如果p不是素数的话,根据性质3,有原根的p最多有两个素因子,

在judge函数里记录一下,是素数还是不是素数,有哪几个素因子,

这样如果有原根的话就能顺便把phi(p)也给求了

 

URAL-1268 求最大原根(保证输入是一个3<=p<=65536的奇素数)

这题时限250ms,卡的比较紧,T了两发280ms

(1)把回答过的记忆化一下,毕竟不超过65536的奇素数估计也就4k,然而有6k多个询问

(2)是开vector预处理各个数的所有素因子,枚举素因子向vector里放,

每个数素因子不超过6个(2*3*5*7*11*13约等于3W),大概就是O(6*n)的叭,然后就卡过了

(3)从大到小枚举 比 从小到大找出原根再用gcd搞一个最大的原根 要快一些

(4)题目说是奇素了,把判是否存在原根的那些都杠掉

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<cmath> 
#include<algorithm>
using namespace std;
const int maxn=(1<<16)+10;
typedef long long ll;
bool ok[maxn];
vector<ll>cal,rt;
vector<ll>divisor[maxn];
ll p,phi[maxn],prime[maxn],ans[maxn],cnt;
ll modpow(ll x,ll n,ll mod)
{
	if(!n)return 1;
	ll p=modpow(x,n/2,mod),q=p*p%mod;
	if(n&1)q=q*x%mod;
	return q;
}
void sieve()
{ 
    phi[1]=1;
	for(ll i=2;i<maxn;++i)
	{
		if(!ok[i])
		{
			prime[cnt++]=i;
			phi[i]=i-1;
			ans[i]=-1;
		}
		for(int j=0;j<cnt;++j)
		{
			if(i*prime[j]>=maxn)break;
			ok[i*prime[j]]=1;
			if(i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];//prime[j]是i的因子 prime[j]的素因子项包含在i的素因子项里
				break; 
			}
			else phi[i*prime[j]]=phi[i]*(prime[j]-1);//prime[j]与i互质 phi[i*prime[j]=phi[i]*phi[prime[j]]
		}
	}
	for(int k=0;k<cnt;++k)
	{
		ll &i=prime[k];
		for(ll j=i;j<maxn;j+=i)
		{
			divisor[j].push_back(i);			
		}
	}
}
bool judge(ll p)
{
	if(p%2==0)p/=2;
	if(!ok[p])return 1; 
	if(p%2==0)return 0;
	for(int i=1;i<cnt;++i)//得找奇素数 i=1很关键 
	{
		if(p%prime[i]==0)
		{
			while(p%prime[i]==0)
			p/=prime[i];
			return p==1;
		}
	}
	return 0;
}
ll solve(ll p)
{ 
	if(p==2)return 1;
	if(p==4)return 3;
	//if(!judge(p))return -1; 保证是素数 
	ll root,now=1,final=0;//phi(p)
	cal=divisor[phi[p]];
	int len=cal.size();
	for(ll a=p-1;a>=2;--a)
	{
	    if(modpow(a,phi[p],p)!=1)continue;
	    bool flag=1;
		for(int i=0;i<len&&flag;++i)
		{
			if(modpow(a,phi[p]/cal[i],p)==1)flag=0;
		}
		if(flag)
		{
			root=a;
			return root;
		}
	}
}
int main()
{
	int t;
	scanf("%d",&t);
	sieve(); 
	while(t--)
	{
	 scanf("%lld",&p);
	 if(ans[p]!=-1)printf("%lld\n",ans[p]);
	 else printf("%lld\n",ans[p]=solve(p));
    }
	return 0;
}

 

应用

①如果模p意义下问一些数a[]的乘积的话,可以把a[]压缩成原根对应的值,变乘法为加法

Codeforces Round #538 (Div. 2) F - Please, another Queries on Array?

2018秦皇岛ccpc-camp Steins-Gate (原根+FFT)

 

②NTT,用最小原根的板子搞出原根来,才能做

 

③BSGS,当模的数p很大而需要被映射的值v很少的时候,

无法for循环一遍1-p-1,把值映射成原根对应的幂次,

用BSGS,对于每个v,是O(sqrt(p))的复杂度

 

参考概念及预备知识(上述证明符号不懂的看这里)

①模m剩余类(Zm)的概念

其实也就是离散学的一种同余关系,最普遍的那种模意义下的同余关系

②环的概念&&单位元的概念&&零因子的概念&&可逆元的概念

③可逆元与欧拉函数的关系

即ax==1(mod m)方程有解的充要条件是(a,m)==1

④可逆元的个数

Z_{m}^{*}的概念&&群的概念&&群的阶

注意到Z_{m}^{*}的加法不封闭,乘法封闭,因此Z_{m}^{*}只有一种运算,

两种运算+blahblah是环,一种运算+blahblah是群

Z_{m}^{*}为可逆元的集合,元素个数为可逆元个数即\varphi(m)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值