前言:
本来想写BSGS算法,但是笔者今天想打游戏 感觉先写原根会更好一点,所以我们今天重点讨论一下什么是原根、哪些整数有原根、原根的性质和求解。
提要:
为了减少后面的阅读障碍,先简单介绍一下欧拉函数
,主要部分后面放积性函数那介绍。(ps:书上符号写的是
,但网上写的是
,不过这不重要)
欧拉函数
的函数值是,小于
且与
互质的正整数的个数。
比如
,因为小于
的正整数中,仅有
与
与其互质。
容易发现,当
为素数时,有
。
还有欧拉定理:若
,则
。
差不多先这些,开始正文。
整数的阶:
什么是阶?
设
和
是互素的整数,
,使得
成立的最小正整数
,称为
模
的阶,符号表示为
。
比如要求
模
的阶,我们一一列举,
。
那么我们称
模
的阶就是
,记为
。
现在我们观察方程
。
那么显然根据欧拉定理,我们可以知道
是方程的一个解,但它未必是最小的,所以不一定是阶,而当
是
模
的阶时,我们称
为
的一个原根,这个放后面介绍。
现在继续来讲阶。
定理一:若
,则
是方程
的一个解的充要条件是
。(其中
是整除符号,表示
整除
)
这个必要性很好证明,既然
,那
当然也成立。
然后充分性,假设存在
不整除
且满足方程,令
(其中
不整除
)满足方程,那么
一定比
小,且满足
,那它才是原根嘛。
这样就证好了。
定理二:若
,则
能整除
。
前面看下来应该也能想到,且由定理一发现显然嘛。
这时候我们容易想到一个
求阶的办法,因为
的阶一定是
的因子,所以可以暴力枚举其因子,再看看是不是满足条件。
定理三:若
,则
的充要条件是
。
定理比较重要,自己意会一下。
定理四:若
,且
为正整数,则有
。
打个比方体会一下,
,那么就有
。其实就是这个
的因子,这样想就容易理解多了吧。
原根:
当
模
的阶为
,也就是说当且仅当
是
的倍数,使得
成立,此时称
为
的原根。
那么知道了
的原根
后,有什么好处呢,可以举一个例子。
我们举998244353的原根3, 我们举一个素数
的一个原根
,然后列举
的幂次模
。
。
然后发现这些余数构成了一个模
的完全剩余系
,也就是对于任意
,都可以找到
使得
。
这是素数的情况,现在我们来康康非素数的情况,举
的一个原根
。然后列举
的幂次模
。
很容易发现以
个为一组,那这个
是什么呢?
其实是
。然后出现的数字
,正是与
互质的那
个数啊。
那么前面素数的情况也说得通了。
所以我们就可以得到,
定理一:若
,则
构成模
的既约剩余系。
大概懂了就不证明了。
而当
为素数时,我们就可以将求解方程
的问题代换为求解
,其中
是
的原根,因为
的幂次是模
的完全剩余系,所以可以用
来代换
,这样我们只要求出新方程的
,那么原方程的解就可以求出来了。
下一个,
定理二:如果一个数
有原根,那么他有多少个不同余的原根,
答案是
个。
伪证明:由前面阶那里的定理四,我们可以知道
,那么当
为
的原根时,就有
。
于是我们得到,
。
所以要使
等于
,就要满足
,在
到
范围里自然就有
个满足条件的。
吃饭去了,回来再写。
现在研究哪些数是有原根的,也就是原根的存在性。
因为刚吃了饭,我懒了。
先证明素数都是有原根的。
再证明奇素数的幂都是有原根的。
然后讨论关于
的幂次,发现
和
是有原根的,其他
的幂次都没有原根。
然后再发现
乘上奇素数的幂也是有原根的。
最后再证明剩下的都没有原根。
于是,得出结论:
定理三:
和
,和奇素数的正整数幂次
,以及
乘上奇素数的正整数幂次
都是有原根的。
最后,再研究一下怎么求
的原根。传送门
先给出一种暴力的解法。(没有第二种了)
先判断有无原根。
的原根是
,
的原根是2,
的原根是
直接特判掉。
然后我们暴力枚举
,再判断它是不是
的原根,枚举的时候当然还要保证与模数互质。
至于要判断
是不是
的原根,只要看是否存在
比
小,且满足
。
但是我们不至于再去一个一个枚举
,因为我们知道它的阶是满足方程的,而阶的倍数也是满足方程的。所以我们只要将
质因子分解成
。
然后枚举每个质因子,判断是否存在
。若存在,说明阶比
小,则不是原根;反之,则是原根。
大力出奇迹,不愧是大力。
最后贴代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll gcd(ll a,ll b)
{
return b?gcd(b,a%b):a;
}
ll fpow(ll a,ll n,ll p)
{
ll sum=1,base=a%p;
while(n!=0)
{
if(n%2)sum=sum*base%p;
base=base*base%p;
n/=2;
}
return sum;
}
vector<ll> getPrimFac(ll n)
{
vector<ll>fac;
fac.clear();
for(ll i=2;i*i<=n;i++)
{
if(n%i==0)
{
fac.push_back(i);
while(n%i==0)n/=i;
}
}
if(n>1)fac.push_back(n);
return fac;
}
bool hasRoot(ll n)
{
if(n==2||n==4)return true;
if(n<=1||n%4==0)return false;
ll num=0;
while(n%2==0)n/=2;
for(ll i=3;i*i<=n;i++)
{
if(n%i==0)
{
num++;
while(n%i==0)n/=i;
}
}
if(n>1)num++;
if(num==1)return true;
return false;
}
ll getPhi(ll n)
{
ll ans=n;
for(ll i=2;i*i<=n;i++)
{
if(n%i==0)
{
ans=ans/i*(i-1);
while(n%i==0)n/=i;
}
}
if(n>1)ans=ans/n*(n-1);
return ans;
}
ll getRoot(ll n)
{
if(!hasRoot(n))return -1;//不存在原根返回-1
if(n==2)return 1;
if(n==3)return 2;
if(n==4)return 3;
ll w=getPhi(n);
vector<ll>fac=getPrimFac(w);
for(ll i=2;i<w;i++)
{
if(gcd(i,n)!=1)continue;
ll is=1;
for(ll j=0;j<fac.size();j++)
{
if(fpow(i,w/fac[j],n)==1)is=0;
}
if(is)return i;
}
return -1;
}
int main()
{
ll p;
scanf("%lld",&p);
printf("%lld\n",getRoot(p));
return 0;
}