【8】原根学习笔记
前言
多校联训 2025 暑假 Day 1
有些性质没有用到或很显然,等以后用到了再记证明。其实是不会证
前置知识:【7】同余学习笔记,【7】欧拉函数学习笔记
阶
概念
定义:给定 \(a,m\in\mathbb{N^+}\),满足 \(a^n\equiv1\pmod m\) 的最小的正整数 \(n\) 叫做 \(a\) 模 \(m\) 的阶,记作 \(\delta_m(a)\) 或 \(\text{ord}_m(a)\)。
性质 \(1\):\(a,a^2,\dots,a^{\delta_m(a)}\) 模 \(m\) 两两不同余。
考虑反证,假设存在 \(1\le i\lt j\le\delta_m(a)\) 使 \(a^i\equiv a^j\pmod{m}\),则 \(a^{j-i}=1\pmod{m}\)。显然 \(j=i\lt\delta_m(a)\),与阶的最小性矛盾。原结论得证。
性质 \(2\):若 \(a^n\equiv1\pmod{m}\),则 \(\delta_m(a)\mid n\)。
对 \(n\) 做带余除法,设 \(n=\delta_m(a)q+r\)。带回原式。
由带余除法 \(r\lt\delta_m(a)\),与阶的最小性矛盾。原结论得证。
推论 \(1\):若 \(a^p\equiv a^q\pmod m\),则 \(p\equiv q\pmod{\delta_m(a)}\)。
性质 \(3\):若 \(\gcd(a,m)=\gcd(b,m)=1\),则 \(\delta_m(ab)=\delta_m(a)\delta_m(b)\) 的充要条件是 \(\gcd(\delta_m(a),\delta_m(b))=1\)。
性质 \(4\):若 \(\gcd(a,m)=1\),则 \(\delta_m(a^k)=\frac{\delta_m(a)}{\gcd(\delta_m(a),k)}\)。
求法
考虑使用试除法,由阶的性质 \(2\),阶一定是 \(\varphi(m)\) 的因数。枚举每一个质因数,除掉这个质因数后快速幂判断是否模意义下等于 \(1\)。正确性显然。
代码中 \(d\) 数组为 \(\varphi(m)\) 去重后质因数的集合。使用朴素分解质因数复杂度为 \(O(\sqrt{n}+\log^2m)\),使用 Pollard-rho 可以做到 \(O(n^{0.25}\log n+\log^2m)\)。
long long order(long long x,long long mod)
{
long long now=phi;
for(int i=1;i<=cnt;i++)
while(now%d[i]==0&&power(x,now/d[i],mod)==1)now/=d[i];
return now;
}
原根
概念
定义:\(m\in\mathbb{N^+},g\in\mathbb{Z}\),若 \(\gcd(g,m)=1\),且 \(\delta_m(g)=\varphi(m)\),则称 \(g\) 为 \(m\) 的原根。
性质:若一个数 \(m\) 有原根,则 \(g^1,g^2,\dots,g^{\varphi(m)}\) 模 \(m\) 互不相等且构成 \(m\) 的简化剩余系。
推论:若 \(m\) 为质数且有原根,则 \(g^1,g^2,\dots,g^{m-1}\) 模 \(m\) 互不相等且构成 \(m\) 的简化剩余系。
这性质和推论十分重要。它的作用包括但不限于定义离散对数,取离散对数降次,NTT。
原根存在定理
一个数存在原根当且仅当 \(m=2,4,p^{\alpha},2p^{\alpha}\),其中 \(p\) 为奇质数,\(\alpha\in\mathbb{N^+}\)。
原根判定定理
设 \(m\ge 2\),则正整数 \(g\) 是 \(m\) 原根的充要条件是 \(\gcd(g,m)=1\) 且对于 \(\varphi(m)\) 的每一个素因数 \(p\) 都有 \(g^{\frac{\varphi(m)}{p}}\not\equiv 1\)。
必要性显然。考虑证明充分性。
假设存在满足要求的数 \(g\) 不是原根,则由欧拉定理有 \(g^{\varphi(m)}\equiv1\pmod m\)。
又因为 \(g\) 不是原根,由阶和原根的定义得 \(\delta_m(g)\lt\varphi(m)\)。由阶的性质 \(2\) 得 \(\delta_m(g)\mid\varphi(m)\)。
因此存在一个 \(\varphi(m)\) 的素因数 \(p\) 满足 \(\delta_m(g)\mid\frac{\varphi(m)}{p}\)。则 \(g^{\frac{\varphi(m)}{p}}\equiv(g^{\delta_m(g)})^k\equiv g^{\delta_m(g)}\equiv1\pmod m\)。因此 \(g\) 是原根,矛盾。原结论成立。
原根数量
若 \(m\) 有原根,则 \(m\) 的原根数量为 \(\varphi(\varphi(m))\)。
最小原根的范围估计
最小原根的范围为 \(\Omega(n^{0.25})\),应用时可以看作 \(O(n^{0.25})\)。
求法
我们先来实现判断一个数是否为原根,直接套用原根判定定理。
代码中 a 表示 \(g\),n 表示 \(\varphi(m)\),mod 表示 \(m\),y 数组表示 \(\varphi(m)\) 的素因数集合。利用 power(a,n,mod)!=1 判断 \(\gcd(g,m)\) 是否等于 \(1\)。
bool check(long long a,long long n,long long mod)
{
if(power(a,n,mod)!=1)return 0;
for(int i=1;i<=tol;i++)
if(power(a,n/y[i],mod)==1)return 0;
return 1;
}
根据最小原根的范围,我们可以在求出 \(\varphi(m)\) 的素因数集合后暴力遍历每个数求出最小原根。
求出最小原根 \(g\) 后,求其他原根是容易的。根据原根的定义,我们枚举 \(g\) 的次幂对 \(m\) 取模可以遍历整个剩余系,且 \(g\) 自乘之后原根判定定理的第一个条件依旧满足。\(g\) 的幂次与 \(\varphi(i)\) 互质时第二个条件也满足,且 \(g\) 的幂次与 \(\varphi(i)\) 不互质时无法计算得到 \(1\),因此直接判断 \(g\) 的幂次与 \(\varphi(i)\) 是否互质就可以判断是否为原根,且可以求出所有原根。
代码中 a[i] 表示数 \(i\) 是否为质数,若 \(a[i]=0\) 表示 \(i\) 为质数。
void getroot(long long n)
{
long long mi=1,ans=0,num=0,b=n,now=1;
tol=0;
if(!r[n])
{
printf("0\n\n");
return;
}
n=phi[n];
if(!a[n])y[++tol]=n;
for(int i=2;i*i<=n;i++)
if(n%i==0)
{
if(!a[i])y[++tol]=i;
if(n/i!=i&&!a[n/i])y[++tol]=n/i;
}
while(!check(mi,n,b))mi++;
for(long long i=1;i<=n;i++)
{
now=now*mi%b;
if(gcd(i,n)==1)ans++,rt[++num]=now;
}
}
复杂度为 \(O(n^{0.25}\log n)\)。
离散对数
定义:取模数 \(m\) 的原根 \(g\),则对满足 \(\gcd(a,m)=1\) 的整数 \(a\),则必然存在唯一的整数 \(0\le k\lt\varphi(m)\) 使 \(g^k\equiv a\pmod m\),那我们称 \(k\) 为以 \(g\) 为底,模 \(m\) 的离散对数,记作 \(k=\text{ind}_{g}\;a\),在不混淆的情况下可以记作 \(\text{ind }a\)。
显然 \(\text{ind}_{g}\;1=0,\text{ind}_{g}\;g=1\)。
性质 \(1\):\(\text{ind}_{g}\;ab\equiv\text{ind}_{g}\;a+\text{ind}_{g}\;b\pmod {\varphi(m)}\)
由原根的性质,得 \(\text{ind}_{g}\;ab\equiv\text{ind}_{g}\;a+\text{ind}_{g}\;b\pmod {\varphi(m)}\)。结论得证。
推论 \(1\):\(\text{ind}_{g}\;a^k\equiv k\text{ind}_{g}\;a\pmod {\varphi(m)}\)
性质 \(2\):若 \(g'\) 也是 \(m\) 的原根,则有 \(\text{ind}_{g}\;a\equiv\text{ind}_{g'}\;a\times\text{ind}_{g}\;g'\pmod {\varphi(m)}\)。
性质 \(3\):\(a\equiv b\pmod m\) 与 \(\text{ind}_{g}\;a= \text{ind}_{g}\;b\) 可以互相推导。
\(a\equiv b\pmod m\) 可以推出 \(g^{\text{ind}_{g}\;a}\equiv g^{\text{ind}_{g}\;b}\pmod m\),由原根的性质得 \(\text{ind}_{g}\;a=\text{ind}_{g}\;b\)。上述每步步步可逆,结论得证。
在实际应用时,离散对数不一定会取到 \(0\le k\lt\varphi(m)\),所以我们有时需要把后面的式子改写为 \(\text{ind}_{g}\;a\equiv\text{ind}_{g}\;b\pmod{\varphi(m)}\)。特别注意,对离散对数的取模都是模 \(\varphi(m)\)。
例题
例题 \(1\) :
原根模板题,线性筛个质数就做完了。
#include <bits/stdc++.h>
using namespace std;
long long t,n,d,y[1000010],pri[1000010],phi[1000010],r[2000010],rt[2000010],cnt=0,tol=0;
bool a[1000010];
void init(long long mx)
{
a[1]=1,phi[1]=1;
for(int i=2;i<=mx;i++)
{
if(!a[i])pri[++cnt]=i,phi[i]=i-1;
for(int j=1;j<=cnt&&i*pri[j]<=mx;j++)
{
a[i*pri[j]]=1;
if(i%pri[j]==0)
{
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
}
r[2]=r[4]=1;
for(int i=2;i<=cnt;i++)
for(long long j=pri[i];j<=mx;j*=pri[i])r[j]=r[j*2]=1;
}
long long gcd(long long x,long long y)
{
if(y==0)return x;
else return gcd(y,x%y);
}
long long power(long long a,long long p,long long mod)
{
long long ans=1,x=a;
while(p)
{
if(p&1)ans=ans*x%mod;
p>>=1;
x=x*x%mod;
}
return ans;
}
bool check(long long a,long long n,long long mod)
{
if(power(a,n,mod)!=1)return 0;
for(int i=1;i<=tol;i++)
if(power(a,n/y[i],mod)==1)return 0;
return 1;
}
void getroot(long long n,long long d)
{
long long mi=1,ans=0,num=0,b=n,now=1;
tol=0;
if(!r[n])
{
printf("0\n\n");
return;
}
n=phi[n];
if(!a[n])y[++tol]=n;
for(int i=2;i*i<=n;i++)
if(n%i==0)
{
if(!a[i])y[++tol]=i;
if(n/i!=i&&!a[n/i])y[++tol]=n/i;
}
while(!check(mi,n,b))mi++;
for(long long i=1;i<=n;i++)
{
now=now*mi%b;
if(gcd(i,n)==1)ans++,rt[++num]=now;
}
sort(rt+1,rt+num+1);
printf("%lld\n",ans);
for(int i=d;i<=num;i+=d)printf("%lld ",rt[i]);
printf("\n");
}
int main()
{
init(1000000);
scanf("%lld",&t);
while(t--)
{
scanf("%lld%lld",&n,&d);
getroot(n,d);
}
return 0;
}
例题 \(2\) :
记录这个小技巧,顺便展示一下离散对数的用法。本题中 \(\text{ind}\) 省略了底数 \(g\)。
先考虑求询问 \(1\sim\sqrt{p}+1\) 内的数的答案。根据离散对数的性质 \(1\),如果一个数 \(x\) 可以分解为两个数 \(a,b\) 的乘积,则有 \(\text{ind }x=\text{ind }a+\text{ind }b\),可以在线性筛的时候顺便维护出来。
于是我们只需要对 \(1\sim\sqrt{p}+1\) 内 \(y\) 为质数的情况求 BSGS。设 BSGS 分块的阈值为 \(B\),由于本题是预处理 \(O(B)\) 查询 \(O(\pi(\sqrt{p})\frac{p}{B})\),\(B\) 需要取为 \(\sqrt{p\sqrt{p}\log\sqrt{p}}\) 复杂度均衡。
若查询 \(1\sim\sqrt{p}+1\) 的数,直接返回答案。否则,我们把 \(p\) 对输入的数 \(y\) 做带余除法,有 \(p=vy+r\)。由于 \(y\gt\sqrt{p}+1\),所以 \(v\le\sqrt{p}\)。
变形一下有 \(y=\frac{p-r}{v}\),两边取离散对数有 \(\text{ind }y\equiv\text{ind }(p-r)-\text{ind }v\)。由离散对数性质 \(3\),有 \(\text{ind }y\equiv\text{ind }(p-1)+\text{ind }r-\text{ind }v\)。
我们又有 \(p=(v+1)y+r-y\),变形一下有 \(y=\frac{p+y-r}{v+1}\),两边取以 \(g\) 为底的离散对数有 \(\text{ind }y\equiv\text{ind }(p+y-r)-\text{ind }(v+1)\)。由离散对数性质 \(3\),有 \(\text{ind }y\equiv\text{ind }(y-r)-\text{ind }(v+1)\)。
观察两式,\(\text{ind }v,\text{ind }(v+1)\) 因为 \(v\le\sqrt{p}\) 已经被求出,而 \(\text{ind }(p-1)\) 可以预处理,因此不需要管它们。而 \(\min(r,y-r)\le\frac{y}{2}\),因此如果每次我们选择 \(r\) 或 \(y-r\) 较小的一项的递归下去,每次 \(y\) 至少减半,直到 \(\sqrt{p}+1\),可以在 \(\log\) 的时间范围内求出结果。
时间复杂度预处理 \(O(\frac{p^{0.75}}{\sqrt{\log p}})\),单次询问 \(O(\log p)\)。注意离散对数的值是模 \(\varphi(p)\)。
#include <bits/stdc++.h>
using namespace std;
int p,g,q,y,h,b,pr[100000],lg[100000],cnt=0,ap=0;
bool vis[100000];
unordered_map<int,int>t;
int power(int a,int q)
{
int ans=1,x=a;
while(q)
{
if(q&1)ans=1ll*ans*x%p;
q>>=1;
x=1ll*x*x%p;
}
return ans;
}
int bsgs(int y)
{
int inv=power(power(g,b),p-2),siv=y;
for(int i=0;i<=p/b;i++)
{
if(t.count(siv))return i*b+t[siv]-1;
siv=1ll*inv*siv%p;
}
return -1;
}
void init(int mx)
{
int now=1;
for(int i=0;i<b;i++)
{
if(!t[now])t[now]=i+1;
else t[now]=min(t[now],i+1);
now=1ll*now*g%p;
}
vis[1]=1;
for(int i=1;i<=mx;i++)
{
if(!vis[i])pr[++cnt]=i,lg[i]=bsgs(i);
for(int j=1;j<=cnt&&i*pr[j]<=mx;j++)
{
vis[i*pr[j]]=1,lg[i*pr[j]]=(lg[i]+lg[pr[j]])%(p-1);
if(i%pr[j]==0)break;
}
}
}
int fdl(int y)
{
if(y==p-1)return ap;
if(y<=h+1)return lg[y];
int v=p/y,r=p-v*y;
if(r<=y-r)return ((fdl(p-1)+fdl(r))%(p-1)+p-1-fdl(v))%(p-1);
return (fdl(y-r)+p-1-fdl(v+1))%(p-1);
}
int main()
{
scanf("%d%d%d",&p,&g,&q);
h=sqrt(p),b=sqrt(1ll*p*h/log(h));
init(h+1),ap=bsgs(p-1);
while(q--)
{
scanf("%d",&y);
printf("%d\n",fdl(y));
}
return 0;
}
例题 \(3\) :
由于 \(m\) 是奇质数的幂,所以存在原根 \(g\)。
两边取以 \(g\) 为底的离散对数,利用离散对数的推论 \(1\) 降次。由于左边乘了一个 \(a\),右边的范围不一定在 \(0\le k\lt\varphi(m)\) 范围内,所以我们用同余式来表述。注意是模 \(\varphi(m)\)。本题中 \(\text{ind}\) 省略了底数 \(g\)。
由于题目问是否存在 \(a\),又因为同余式可以被转化为裴蜀等式,裴蜀等式可以判有无解,考虑转化为裴蜀等式。
由裴蜀定理,有解需要满足如下条件。
先考虑左边怎么求。\(\gcd(\text{ind }x,\varphi(m))\) 可以由 \(\text{lcm}(\text{ind }x,\varphi(m))\) 变形得到,而 \(\text{lcm}\) 是与最小性和倍数有关。联想到阶的定义与性质 \(2\),我们考虑利用阶来构造 \(\gcd(\text{ind }x,\varphi(m))\)。
由阶的定义,有 \(x^{\delta_m(x)}\equiv1\pmod m\)。由离散对数的定义,有 \(g^{\text{ind }x}\equiv x\pmod m\)。把后面式子带入前面得如下式子。
由原根的定义,有 \(\delta_m(g)=\varphi(m)\)。由阶的性质 \(2\),有如下式子。
由于 \(\delta_m(x)\) 的最小性,得到 \(\delta_m(x)=\frac{\text{lcm}(\varphi(m),\text{ind }x)}{\text{ind }x}\)。展开成 \(\gcd\) 移项得到最终的表达式。
回到本来的式子,考虑应用这个结论,我们发现 \(\gcd(\text{ind }x,\varphi(m))\mid\text{ind }y\) 其实等价于 \(\gcd(\text{ind }x,\varphi(m))\mid\gcd(\text{ind }y,\varphi(m))\),同理得右边为 \(\frac{\varphi(m)}{\delta_m(y)}\)。
Pollard-rho 分解质因数算 \(\varphi\) 和阶即可。时间复杂度 \(O(m^{0.25}\log m+n\log^2m)\)。
#include <bits/stdc++.h>
using namespace std;
long long m,n,x,y,st[10000],d[10000],cnt=0,phi=0;
mt19937 rd(time(NULL));
long long gcd(long long x,long long y)
{
if(y==0)return x;
return gcd(y,x%y);
}
long long power(long long a,long long p,long long mod)
{
long long x=a%mod,ans=1;
while(p)
{
if(p&1)ans=(__int128)ans*x%mod;
p>>=1;
x=(__int128)x*x%mod;
}
return ans;
}
bool check(long long x,long long bas)
{
long long p=x-1,top=1;
while(!(p&1))p>>=1,top++;
st[top]=power(bas,p,x);
for(int i=top-1;i>=1;i--)st[i]=(__int128)st[i+1]*st[i+1]%x;
if(st[1]!=1)return 0;
for(int i=2;i<=top;i++)
if(st[i]==x-1)return 1;
else if(st[i]!=1&&st[i]!=x-1)return 0;
return 1;
}
bool miller_rabin(long long x)
{
if(x==1)return 0;
long long pr[12]={2,3,5,7,11,13,17,19,23,29,31,37};
for(int i=0;i<12;i++)
if(x==pr[i])return 1;
for(int i=0;i<12;i++)
if(x%pr[i]==0)return 0;
for(int i=0;i<12;i++)
if(!check(x,pr[i]))return 0;
return 1;
}
long long pollard_rho(long long x)
{
long long now=0,d=rd()%(x-1)+1,lim=1;
while(lim)
{
long long pr=now,sum=1;
for(int i=1;i<=lim;i++)
{
now=((__int128)now*now%x+d)%x,sum=(__int128)sum*abs(now-pr)%x;
if(i%127==0&&gcd(sum,x)>1)return gcd(sum,x);
}
lim<<=1;
if(gcd(sum,x)>1)return gcd(sum,x);
}
return x;
}
void divide(long long x)
{
long long q[500],l=1,r=0;
q[++r]=x,cnt=0;
while(l<=r)
{
if(miller_rabin(q[l]))d[++cnt]=q[l];
else
{
long long d=pollard_rho(q[l]);
while(d==q[l])d=pollard_rho(q[l]);
q[++r]=d,q[++r]=q[l]/d;
}
l++;
}
}
long long order(long long x,long long mod)
{
long long now=phi;
for(int i=1;i<=cnt;i++)
while(now%d[i]==0&&power(x,now/d[i],mod)==1)now/=d[i];
return now;
}
int main()
{
scanf("%lld%lld",&m,&n);
divide(m),sort(d+1,d+cnt+1),cnt=unique(d+1,d+cnt+1)-d-1,phi=m;
for(int i=1;i<=cnt;i++)phi=phi/d[i]*(d[i]-1);
divide(phi),sort(d+1,d+cnt+1),cnt=unique(d+1,d+cnt+1)-d-1;
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&x,&y);
long long g1=phi/order(x,m),g2=phi/order(y,m);
if(g2%g1==0)printf("Yes\n");
else printf("No\n");
}
return 0;
}
后记
OI 会用到的初等数论,应该,都,学完了吧?
灼日之矢 与心门轻擦
寒月旧盟 重逢与月下
三界之外 斗战自洒踏
倒打一耙 倾力冲垮

浙公网安备 33010602011771号