【学习笔记】原根
阶
定义
- 若满足 \(a^{n} \equiv 1 \pmod{p}\) 的最小正整数 \(n\) 存在,则称 \(n\) 为 \(a\) 模 \(p\) 的 阶,记为 \(\delta_{p}(a)\) 或 \(\operatorname{ord}_{p}(a)\)。
- 由欧拉定理可知对于 \(a \in \mathbf{Z},p \in \mathbb{N}^{*}\) 若 \(\gcd(a,p)=1\) 则 \(a^{\varphi(p)} \equiv 1 \pmod{p}\),此时有 \(\delta_{p}(a) \le \varphi(p)\)。
- 类似地,若满足 \(a^{n} \equiv -1 \pmod{p}\) 的最小正整数 \(n\) 存在,则称 \(n\) 为 \(a\) 模 \(p\) 的 半阶,记为 \(\delta_{p}^{-}(a)\)。
性质
- 性质 \(1\):\(\forall i,j(i \ne j)\in [1,\delta_{p}(a)],a^{i} \not\equiv a^{j} \pmod{p}\)。
- 性质 \(2\):若 \(a^{n} \equiv 1 \pmod{p}\),则有 \(\delta_{p}(a)|n\)。
- 证明
- 假设 \(\delta_{p}(a) \nmid n\),不妨设 \(n=k \delta_{p}(a)+r(1 \le r< \delta_{p}(a))\)。
- 此时有 \(a^{n}=(a^{\delta_{p}(a)})^{k}a^{r} \equiv a^{r} \equiv 1 \pmod{p}\),由阶最小性可知 \(r=0\) 与假设矛盾。
- 证明
- 性质 \(3\):若 \(a^{u} \equiv a^{v} \pmod{p}\) ,则有 \(u \equiv v \pmod{\delta_{p}(a)}\)。
- 性质 \(4\):对于 \(a,b \in \mathbf{Z},p \in \mathbb{N}^{*},\delta_{p}(ab)=\delta_{p}(a)\delta_{p}(b)\) 的充要条件为 \(\gcd(\delta_{p}(a),\delta_{p}(b))=1\)。
- 证明
- 必要性
- 由 \(a^{\delta_{p}(a)} \equiv 1 \pmod{p},b^{\delta_{p}(b)} \equiv 1 \pmod{p}\) 联立得到 \((ab)^{\operatorname{lcm}(\delta_{p}(a),\delta_{p}(b))} \equiv 1 \pmod{p}\)。
- 由性质 \(2\) 有 \(\delta_{p}(ab)=\delta_{p}(a)\delta_{p}(b)|\operatorname{lcm}(\delta_{p}(a),\delta_{p}(b))\),得到 \(\gcd(\delta_{p}(a),\delta_{p}(b))=1\)。
- 充分性
- 由 \(\begin{cases} 1 \equiv (ab)^{\delta_{p}(ab)\delta_{p}(b)} \equiv a^{\delta_{p}(ab)\delta_{p}(b)} \pmod{p} \\ 1 \equiv (ab)^{\delta_{p}(ab)\delta_{p}(a)} \equiv b^{\delta_{p}(ab)\delta_{p}(a)} \pmod{p} \end{cases}\),得到 \(\begin{cases} \delta_{p}(a)|\delta_{p}(ab)\delta_{p}(b) \\ \delta_{p}(b)|\delta_{p}(ab)\delta_{p}(a) \end{cases}\),进一步得到 \(\delta_{p}(a),\delta_{p}(b)|\delta_{p}(ab)\),即 \(\delta_{p}(a)\delta_{p}(b)|\delta_{p}(ab)\)。
- 类似地,有 \((ab)^{\delta_{p}(a),\delta_{p}(b)} \equiv (a^{\delta_{p}(a)})^{\delta_{p}(b)}(b^{\delta_{p}(b)})^{\delta_{p}(a)} \equiv 1 \pmod{p}\),得到 \(\delta_{p}(ab)|\delta_{p}(a)\delta_{p}(b)\)。
- 故 \(\delta_{p}(ab)=\delta_{p}(a)\delta_{p}(b)\)。
- 必要性
- 证明
- 性质 \(5\):\(\delta_{p}(a^{k})=\frac{\delta_{p}(a)}{\gcd(\delta_{p}(a),k)}\)。
- 证明
- 由 \(1 \equiv (a^{k})^{\delta_{p}(a^{k})} \equiv a^{k\delta_{p}(a^{k})} \pmod{p}\) 可知 \(\delta_{p}(a)|k\delta_{p}(a^{k})\),移项得到 \(\frac{\delta_{p}(a)}{\gcd(\delta_{p}(a),k)}|\delta_{p}(a^{k})\)。
- 又因为 \((a^{k})^{\frac{\delta_{p}(a)}{\gcd(\delta_{p}(a),k)}}=(a^{\delta_{p}(a)})^{\frac{k}{\gcd(\delta_{p}(a),k)}} \equiv 1 \pmod{p}\),可知 \(\delta_{p}(a^{k})|\frac{\delta_{p}(a)}{\gcd(\delta_{p}(a),k)}\)。
- 故 \(\delta_{p}(a^{k})=\frac{\delta_{p}(a)}{\gcd(\delta_{p}(a),k)}\)。
- 证明
求法
- 不断尝试剔除 \(\varphi(p)\) 的素因子即可。
原根
定义
- 若 \(g \in \mathbf{Z},p \in \mathbb{N}^{*},\gcd(g,p)=1\) 且 \(\delta_{p}(g)=\varphi(p)\),则称 \(g\) 为模 \(p\) 的 原根。
- 当 \(p\) 为质数时由于 \(\varphi(p)=p-1\) 化简得到 \(\forall i,j(i \ne j)\in [1,p-1],g^{i} \not\equiv g^{j} \pmod{p}\)。
原根判定定理
- \(g\) 是模 \(p\) 的原根当且仅当对于 \(\varphi(p)\) 的每个素因子 \(\alpha\) 都有 \(g^{\frac{\varphi(p)}{\alpha}} \not\equiv 1 \pmod{p}\) 。
- 证明
- 必要性由阶的最小性可知。
- 充分性
- 假设在满足后面条件的情况下存在一个 \(g\) 不是模 \(p\) 的原根。
- 由欧拉定理可知 \(g^{\varphi(p)} \equiv 1 \pmod{p}\),此时有 \(\delta_{p}(g)|\varphi(p)\) 且 \(\delta_{p}(g)<\varphi(p)\),说明存在素因子 \(\alpha|\varphi(p)\) 使得 \(\delta_{p}(g)|\frac{\varphi(p)}{\alpha}\),代入得到 \(g^{\frac{\varphi(p)}{\alpha}} \equiv 1 \pmod{p}\),与假设不符。
- 证明
原根个数
- 若 \(p\) 有原根,则在模意义下它原根的个数为 \(\varphi(\varphi(p))\)。
- 证明
- 设 \(g\) 为模 \(p\) 的原根,则有 \(\delta_{p}(g^{k})=\dfrac{\delta_{p}(g)}{\gcd(\delta_{p}(g),k)}=\dfrac{\varphi(p)}{\gcd(\varphi(p),k)}\)。
- \(g^{k}\) 为模 \(p\) 的原根当且仅当 \(\gcd(\varphi(p),k)=1\),可行的 \(k\) 只有 \(\varphi(\varphi(p))\) 个。
- 证明
原根存在定理
- \(p\) 有原根当且仅当 \(p=2,4,\alpha^{\beta},2\alpha^{\beta}\),其中 \(\alpha\) 为奇素数,\(\beta \in \mathbb{N}^{*}\)。
- 证明我不会,建议去看 OI Wiki。
最小原根的范围估计
- 王元和 Burgess 证明了素数 \(p\) 的最小原根 \(g_{p}=O(p^{0.25+\epsilon})\),其中 \(\epsilon>0\)。事实上,由大量试验数据可以发现,对于足够大的 \(p\),其最小正原根的大小不是多项式级别的。
- Fridlander 和 Salié 证明了素数 \(p\) 的最小原根 \(g_{p}=\Omega(\log{p})\)。
- 一般情况下暴力找一个数的最小原根的时间复杂度是可以接受的。
常用素数原根
- 详见 素数原根表 。
模意义下的离散对数
定义
- 对于存在原根 \(g\) 的 \(p\) ,若 \(\gcd(a,p)=1\) 则必唯一存在 \(k \in [0,\varphi(p))\) 使得 \(g^{k} \equiv a \pmod{p}\) ,则称 \(k\) 为以 \(g\) 为底时模 \(p\) 的离散对数,记作 \(\gamma(a)=k\)。
- \(\gamma(a)\) 具有对数函数的大部分性质,例如完全加性及其他运算性质。
求法
- 考虑 BSGS。
- 设 \(x=\gamma(a)=Bi+j \in [0,\varphi(p)]\),其中 \(j \in [0,B),i \in [0,\left\lfloor \frac{\varphi(p)}{B} \right\rfloor]\)。将 \(g^{\gamma(a)}=g^{x}=g^{Bi+j} \equiv a \pmod{p}\) 移项得到 \(g^{j} \equiv a g^{-Bi} \pmod{p}\)。
- 取 \(B=O(\sqrt{\varphi(p)})=O(\sqrt{p})\) 来平衡复杂度。
例题
luogu P10515 转圈
luogu P6091 【模板】原根
-
一种方法是线筛预处理出 \(\varphi\) 并标记出是否存在原根后,暴力找一个数的最小原根然后求出所有原根。
点击查看代码
ll prime[1000010],vis[1000010],phi[1000010],pr[1000010],len=0; vector<ll>result,ans; void isprime(ll n) { memset(vis,0,sizeof(vis)); phi[1]=1; for(ll i=2;i<=n;i++) { if(vis[i]==0) { len++; prime[len]=i; phi[i]=i-1; } for(ll j=1;j<=len&&i*prime[j]<=n;j++) { vis[i*prime[j]]=1; if(i%prime[j]==0) { phi[i*prime[j]]=phi[i]*prime[j]; break; } else phi[i*prime[j]]=phi[i]*(prime[j]-1); } } pr[2]=pr[4]=1; for(ll i=2;i<=len;i++) { for(ll j=1;j*prime[i]<=n;j*=prime[i]) pr[j*prime[i]]=1; for(ll j=2;j*prime[i]<=n;j*=prime[i]) pr[j*prime[i]]=1; } } ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) ans=ans*a%p; b>>=1; a=a*a%p; } return ans; } void divide(ll n) { result.clear(); for(ll i=1;i<=len&&prime[i]*prime[i]<=n;i++) { if(n%prime[i]==0) { result.push_back(prime[i]); while(n%prime[i]==0) n/=prime[i]; } } if(n>1) result.push_back(n); } bool check(ll x,ll p) { if(qpow(x,phi[p],p)!=1) return false; for(ll i=0;i<result.size();i++) { if(qpow(x,phi[p]/result[i],p)==1) return false; } return true; } ll min_pr(ll p) { if(pr[p]==0) return -1; for(ll i=1;i<=p-1;i++) { if(check(i,p)==true) return i; } return -1; } void all_pr(ll p,ll g) { ans.clear(); if(g==-1) return; for(ll i=1,x=g;i<=phi[p];i++,x=x*g%p) { if(__gcd(i,phi[p])==1) ans.push_back(x); } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll t,p,d,i,j; cin>>t; isprime(1000000); for(j=1;j<=t;j++) { cin>>p>>d; divide(phi[p]); all_pr(p,min_pr(p)); sort(ans.begin(),ans.end()); cout<<ans.size()<<endl; for(i=1;i<=ans.size()/d;i++) { cout<<ans[i*d-1]<<" "; } cout<<endl; } return 0; }
-
另一种方法是注意到原根比较密集,且在模意义下我们只需要找到任意一个原根即可。又因为 \(\frac{\varphi(\varphi(p))}{p}\) 在可行范围内不会很大,不妨直接将原来 \(O(p^{0.25+\epsilon})\) 找最小原根改为在 \([1,p)\) 范围内随机选择若干个数判断,可以证明随着选择次数的增多找不到原根的概率越来越接近 \(0\)。
点击查看代码
ll prime[1000010],vis[1000010],phi[1000010],pr[1000010],len=0; vector<ll>result,ans; void isprime(ll n) { memset(vis,0,sizeof(vis)); phi[1]=1; for(ll i=2;i<=n;i++) { if(vis[i]==0) { len++; prime[len]=i; phi[i]=i-1; } for(ll j=1;j<=len&&i*prime[j]<=n;j++) { vis[i*prime[j]]=1; if(i%prime[j]==0) { phi[i*prime[j]]=phi[i]*prime[j]; break; } else phi[i*prime[j]]=phi[i]*(prime[j]-1); } } pr[2]=pr[4]=1; for(ll i=2;i<=len;i++) { for(ll j=1;j*prime[i]<=n;j*=prime[i]) pr[j*prime[i]]=1; for(ll j=2;j*prime[i]<=n;j*=prime[i]) pr[j*prime[i]]=1; } } ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) ans=ans*a%p; b>>=1; a=a*a%p; } return ans; } void divide(ll n) { result.clear(); for(ll i=1;i<=len&&prime[i]*prime[i]<=n;i++) { if(n%prime[i]==0) { result.push_back(prime[i]); while(n%prime[i]==0) n/=prime[i]; } } if(n>1) result.push_back(n); } bool check(ll x,ll p) { if(qpow(x,phi[p],p)!=1) return false; for(ll i=0;i<result.size();i++) { if(qpow(x,phi[p]/result[i],p)==1) return false; } return true; } ll arb_pr(ll p) { if(pr[p]==0) return -1; while(1) //如果担心会一直循环下去可以限制枚举次数 { ll g=rand()%(p-1)+1; if(check(g,p)==true) return g; } return -1; } void all_pr(ll p,ll g) { ans.clear(); if(g==-1) return; for(ll i=1,x=g;i<=phi[p];i++,x=x*g%p) { if(__gcd(i,phi[p])==1) ans.push_back(x); } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif srand(time(0)); ll t,p,d,i,j; cin>>t; isprime(1000000); for(j=1;j<=t;j++) { cin>>p>>d; divide(phi[p]); all_pr(p,arb_pr(p)); sort(ans.begin(),ans.end()); cout<<ans.size()<<endl; for(i=1;i<=ans.size()/d;i++) { cout<<ans[i*d-1]<<" "; } cout<<endl; } return 0; }
-
同时可以对求出所有原根时求 \(O(p)\) 次 \(\gcd\) 进行优化,具体地,类似欧拉筛的写法通过 \(\varphi(p)\) 的所有素因子进行标记,具体详见 题解 P6091 【【模板】原根】 | 【模板】原根 题解 。
Gym103428C Assign or Multiply
- 比较经典的套路是通过原根及模意义下的离散对数将若干次原数的乘法运算转化成对数的加法运算。具体来说,设模 \(p\) 意义下分别要乘以 \(a_{1},a_{2}, \dots , a_{k}\),可以转化为模 \(\varphi(p)\) 意义下 \(\gamma(a_{1}),\gamma(a_{2}), \dots,\gamma(a_{k})\) 的相加最后再通过 \(g\) 的若干次幂得到原式。
- 其他部分详见 2025省选模拟5 T2 HZTG5844. A Dance of Fire and Ice 。
T711. 随
luogu P11175 【模板】基于值域预处理的快速离散对数
-
因 \(\gamma\) 是完全加性函数,考虑线筛暴力求出 \(\pi(p)=O(\frac{n}{\ln n})\) 个素数处的离散对数值然后求出非素数处的离散对数。
-
对于一般的 BSGS 来说插入和查询的次数同阶故常取 \(B=O(\sqrt{p})\) 用于平衡复杂度。但在本题中只需要插入 \(1\) 次,但需要查询 \(\pi(p)\) 次,故取 \(B=O(\sqrt{p\pi(p)})\)。
-
因 \(p\) 值域较大,直接线筛空间复杂度不可接受,需要进一步优化。
-
先处理出 \(\sqrt{p}\) 内所有数的离散对数。对于每次询问,
- 若 \(b \le \sqrt{p}\) 则直接回答。
- 否则设 \(p=vb+r,0 \le r \le \sqrt{p},0 \le r \le b-1\)。
- 由 \(p=vb+r\) 可知 \(b=\frac{p-r}{v}\) 进一步得到 \(\gamma(b) \equiv \gamma(-r)-\gamma(v)=\gamma(-1)+\gamma(r)-\gamma(v) \equiv \gamma(p-1)+\gamma(r)-\gamma(v) \pmod{\varphi(p)}\)。
- 由 \(p=(v+1)b+r-b\) 可知 \(b=\frac{p-r+b}{v+1}\) 进一步得到 \(\gamma(b) \equiv \gamma(b-r)-\gamma(v+1) \pmod{\varphi(p)}\)。
- 其中 \(\gamma(p-1)\) 可以通过单独预处理得到。
-
因 \(\min(r,b-r) \le \frac{b}{2}\),故每次选择较小的进行迭代使得 \(b\) 的规模减半直至 \(b \le \sqrt{p}\),最终时间复杂度为 \(O(\dfrac{p^{0.75}}{\log^{0.5} p}+T\log p)\)。
点击查看代码
int p,g,phi,lg[32010],prime[32010],vis[32010],lg_base,klen,len; int qpow(int a,int b,int p) { int ans=1; while(b) { if(b&1) ans=1ll*ans*a%p; b>>=1; a=1ll*a*a%p; } return ans; } struct BSGS { unordered_map<int,int>vis; int k,base; void init(int g,int p) { vis.clear(); k=sqrt(1ll*p*sqrt(p)/log(p))+1; for(int i=0,mi=1;i<=k-1;i++,mi=1ll*mi*g%p) vis[mi]=i; base=qpow(qpow(g,k,p),p-2,p); } int query(int b) { for(int i=0,mi=b;i*k<=phi;i++,mi=1ll*mi*base%p) { if(vis.find(mi)!=vis.end()) return i*k+vis[mi]; } return -1; } }B; void isprime(int n) { lg[1]=0; for(int i=2;i<=n;i++) { if(vis[i]==0) { len++; prime[len]=i; lg[i]=B.query(i); } for(int j=1;j<=len&&i*prime[j]<=n;j++) { vis[i*prime[j]]=1; lg[i*prime[j]]=(lg[i]+lg[prime[j]])%phi; if(i%prime[j]==0) break; } } } int solve(int x) { if(x<=klen) return lg[x]; int v=p/x,r=p%x; if(r<x-r) return ((lg_base+solve(r))%phi-lg[v]+phi)%phi; else return (solve(x-r)-lg[v+1]+phi)%phi; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,b,i; scanf("%d%d%d",&p,&g,&t); klen=sqrt(p)+1; phi=p-1; B.init(g,p); isprime(klen); lg_base=B.query(p-1); for(i=1;i<=t;i++) { scanf("%d",&b); printf("%d\n",solve(b)); } return 0; }
LibreOJ 6542. 离散对数
BZOJ1420 Discrete Root
luogu P5605 小 A 与两位神仙
BZOJ2219 数论之神
参考资料
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18673760,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。