原根

1.定义

原根,是一个数学符号。设m是正整数,a是整数,若a模m的阶等于φ(m),则称a为模m的一个原根。
——百度


阶的定义:
若gcd(a,p)=1,则最小的正整数n使得$a^n≡1(mod\ p) 为a模p的阶(而且一定存在这样一个阶)性质:设gcd(a,p)=1,a模p的阶为n.若正整数N使得为a模p的阶(而且一定存在这样一个阶) 性质: 设gcd(a,p)=1 ,a模p的阶为n. 若正整数N使得apgcda,p=1apn.N使a^N≡1(mod\ p )$, 则 n∣N
设gcd(a,p)=1 ,a模p的阶n整除φ(p)


原根好像有性质
对于正整数m,只有当m=2,4,pap^apa2∗pa2*p^a2pa时m才有原根(p为奇素数,a≥1)
所以可以特判啦
然后呢对于m的原根g,满足gi(mod p)g^i (mod\ p)gi(mod p)(0<=i<=p-2)与1到p-1一一对应

2.求原根


暴力求法
求m的原根时,因为原根一般都比较小,那么就从2开始枚举g,只需要判断g模m的阶是否是φ(m)就好了,怎么判呢,枚举每个数i(1<=i<φ(m))算gi≡1(modm)g^i≡1(mod m )gi1(modm)若满足则这个g一定不是原根
代码

#include<cstdio>
#include<cmath>
inline int phi(int n)
{
    int zc=n,all=sqrt(n);
    for(int i=2;i<=all;i++)
	{
		if(n%i!=0)continue;
    	zc=zc/i*(i-1);
    	while(n%i==0)n/=i;
    }
    if(n>1)zc=zc/n*(n-1);
    return zc;
}
inline int pow(int x,const int y,const int mod)
{
	int res=1;
	for(int i=1;i<=y;i<<=1,x=x*x%mod)if(i&y)res=res*x%mod;
	return res;
}
inline int G(const int m)
{
	const int PHI=phi(m);
	for(int g=2;;g++)
	{
		bool fla=1;
		if(pow(g,PHI,m)!=1)continue;
		for(int i=1;i<PHI;i++)
			if(pow(g,i,m)==1)
			{
				fla=0;
				break;
			}
		if(fla)return g;
	}
}
int m,g;
int main()
{
	scanf("%d",&m);
    g=G(m);
    printf("%d",g);
    return 0;
}

复杂度?
φ(m)*log(m)*O(g)
这个算法在m为大质数的时候根本跑不过


稍快的算法
容易发现,我们在枚举g的时候只要枚举与m互质的数,若不互质,那么一定不满足一一对应的条件
不过很多情况下我们要求的都是一个质数的原根,所以这个优化不常用
代码略


更快的算法
在上面的代码中,容易发现,枚举的i并不是每个每个都有用的,有一个结论,枚举i只需要枚举φ(m)的因数就好了
为什么呢,那么现在就是已知对于所有b(b∣φ(m))b(b|φ(m))b(bφ(m))都不满足gb≡1(mod m)g^b\equiv1(mod\ m)gb1(mod m),求证对于所有b(b不满足b∣φ(m)b|φ(m)bφ(m)1<=b<φ(m)1<=b<φ(m)1<=b<φ(m)),也不会满足gb≡1(mod m)g^b\equiv1(mod\ m)gb1(mod m)
证明:
由欧拉定理可知gφ(m)≡1(mod m)g^{φ(m)}\equiv1(mod\ m)gφ(m)1(mod m)
然后使用反证法,设有合法的b满足gb≡1(mod m)g^b\equiv1(mod\ m)gb1(mod m)(根据已知得出b∣φ(m)b|φ(m)bφ(m)不成立)
设这些存在的b中的最小值为c1c_1c1,即那么gc1≡1(mod m)g^{c_1}\equiv1(mod\ m)gc11(mod m)
c2=φ(m)−c1c_2=φ(m)-c_1c2=φ(m)c1
那么gc2≡1(mod m)g^{c_2}\equiv1(mod\ m)gc21(mod m)
所以满足c2>=c1c_2>=c_1c2>=c1
c1∣c2c_1|c_2c1c2那么设c2=a∗c1c_2=a*c_1c2=ac1(a为整数)
因为φ(m)=c1+c2=(a+1)∗c1φ(m)=c_1+c_2=(a+1)*c_1φ(m)=c1+c2=(a+1)c1所以这个时候c1∣φ(m)c_1|φ(m)c1φ(m),与b∣φ(m)b|φ(m)bφ(m)不成立这个条件冲突
gcd(c1,c2)<c1gcd(c_1,c_2)<c_1gcd(c1,c2)<c1
因为gc1≡1(mod m),gc2≡1(mod m)g^{c_1}\equiv1(mod\ m),g^{c_2}\equiv1(mod\ m)gc11(mod m),gc21(mod m)所以gc2−c1≡1(mod m)g^{c_2-c_1}\equiv1(mod\ m)gc2c11(mod m)
根据gcd的更相减损术原理,容易发现ggcd(c1,c2)≡1(mod m)g^{gcd(c_1,c_2)}\equiv1(mod\ m)ggcd(c1,c2)1(mod m)
又由于gcd(c1,c2)<c1gcd(c_1,c_2)<c_1gcd(c1,c2)<c1所以与c1是最小的那个假设相悖
证毕(在此感谢大佬yx在证明相关的帮助)
代码

#include<cstdio>
#include<cmath>
inline int phi(int n)
{
    int zc=n,all=sqrt(n);
    for(int i=2;i<=all;i++)
	{
		if(n%i!=0)continue;
    	zc=zc/i*(i-1);
    	while(n%i==0)n/=i;
    }
    if(n>1)zc=zc/n*(n-1);
    return zc;
}
inline int pow(int x,const int y,const int mod)
{
	int res=1;
	for(int i=1;i<=y;i<<=1,x=(long long)x*x%mod)if(i&y)res=(long long)res*x%mod;
	return res;
}
int q[200001];
inline int G(const int m)
{
	const int PHI=phi(m);
	q[0]=0;
	for(int i=2;i<PHI;i++)if(PHI%i==0)q[++q[0]]=i;
	for(int g=2;;g++)
	{
		bool fla=1;
		if(pow(g,PHI,m)!=1)continue;
		for(int i=1;i<=q[0];i++)
			if(pow(g,q[i],m)==1)
			{
				fla=0;
				break;
			}
		if(fla)return g;
	}
}
int m,g;
int main()
{
	scanf("%d",&m);
    g=G(m);
    printf("%d",g);
    return 0;
}

一般这个方法就比较有用
复杂度O(m)+O(m)∗O(logm)∗O(g)O(m)+O(\sqrt{m})*O(logm)*O(g)O(m)+O(m)O(logm)O(g)
对于常用模数都绰绰有余
如果你想要更加优越的复杂度,一下这段代码奉上
删去:

q[0]=0;
for(int i=2;i<PHI;i++)if(PHI%i==0)q[++q[0]]=i;

然后加上:

q[0]=0;
const int limit=sqrt(PHI); 
for(int i=2;i<=limit;i++)
	if(PHI%i==0)
	{
		q[++q[0]]=i;
		if(i*i!=PHI)q[++q[0]]=PHI/i;
	}

枚举因数大概可以O(m)O(\sqrt{m})O(m)实现(逃)
那么恭喜,复杂度成了O(m)+O(m)∗O(logm)∗O(g)O(\sqrt{m})+O(\sqrt{m})*O(logm)*O(g)O(m)+O(m)O(logm)O(g)
很快了呐、
最优的算法
发现现在的瓶颈回到了枚举的数量上,其实还可以优化
首先上一个结论,设φ(m)=p1a1∗p2a2∗...∗pnanφ(m)=p_1^{a_1}*p_2^{a_2}*...*p_n^{a_n}φ(m)=p1a1p2a2...pnan(pi为φ(m)的质因数)
那么只要判断
gφ(m)/pi≡1(mod m)(1<=i<=n)g^{φ(m)/p_i}\equiv1(mod\ m)(1<=i<=n)gφ(m)/pi1(mod m)(1<=i<=n)
如果有一个满足,那么当前的g一定不是原根
为什么呢?考虑上面的结论如果对于一个φ(m)的因数b满足了条件,那么在这些数当中一定至少有一个也满足这个条件,然后就好啦
代码依然用替换来给出,替换上文提到的地方

q[0]=0;
const int limit=sqrt(PHI);int zc=PHI;
for(int i=2;i<=limit;i++)
	if(zc%i==0)
	{
		q[++q[0]]=PHI/i;
		while(zc%i==0)zc/=i;
	}
if(zc>1)zc=q[++q[0]]=PHI/zc;

复杂度?O(m)+O(log2m)∗O(g)O(\sqrt{m})+O(log^2m)*O(g)O(m)+O(log2m)O(g)
是不是飞快呢?

3.总结

原根学完了,那要有题目啊对不对
来咯http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1135
51nod上的板子题,可以去码一码哦
然后贴出代码(其实就是上文替换进去的,为了方便还是贴一下吧)

#include<cstdio>
#include<cmath>
inline int phi(int n)
{
    int zc=n,all=sqrt(n);
    for(int i=2;i<=all;i++)
	{
		if(n%i!=0)continue;
    	zc=zc/i*(i-1);
    	while(n%i==0)n/=i;
    }
    if(n>1)zc=zc/n*(n-1);
    return zc;
}
inline int pow(int x,const int y,const int mod)
{
	int res=1;
	for(int i=1;i<=y;i<<=1,x=(long long)x*x%mod)if(i&y)res=(long long)res*x%mod;
	return res;
}
int q[100001];
inline int G(const int m)
{
	const int PHI=phi(m);
	q[0]=0;
	const int limit=sqrt(PHI);int zc=PHI;
	for(int i=2;i<=limit;i++)
		if(zc%i==0)
		{
			q[++q[0]]=PHI/i;
			while(zc%i==0)zc/=i;
		}
	if(zc>1)zc=q[++q[0]]=PHI/zc;
	for(int g=2;;g++)
	{
		bool fla=1;
		if(pow(g,PHI,m)!=1)continue;
		for(int i=1;i<=q[0];i++)
			if(pow(g,q[i],m)==1)
			{
				fla=0;
				break;
			}
		if(fla)return g;
	}
}
int m,g;
int main()
{
	scanf("%d",&m);
    g=G(m);
    printf("%d",g);
    return 0;
}

除此之外呢,还有洛谷P3321 [SDOI2015]序列统计https://www.luogu.org/problemnew/show/P3321
是一道很妙的题,可以去做一做(然而大概需要NTT的前置知识),可以去做哦
原根是个很神奇的东西,一般情况下其实只要知道它是什么并且知道一些常见模数的原根就好了(比如998244353、1004535809的原根都是3),实在忘了就写下代码算下就好了嘛,还有一些妙用,说不定还会在新的题中出现呢,所以值得好好的学习一下

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值