嵌套幂模运算问题(基于扩展欧拉定理)
问题背景
给定一个数组 \(w\)、模数\(m\) 和若干查询 \([l, r]\),要求计算**嵌套幂表达式
\[w_l^{\overset{w_r}{\overset{^{\cdot^{\cdot^{\cdot}}}}{^{w_{l+1}}}}} \mod m
\]
的结果。由于嵌套幂的指数过大( 到了1e9 ),直接计算不可行,须依赖结合快速幂和扩展欧拉定理进行降幂求解。
扩展欧拉定理
在讲解代码前,需先明确扩展欧拉定理的核心内容。定理分为三种情况(对应代码注释中的分类):
对于任意正整数 \(a, b, m\),设 \(φ(m)\) 为 \(m\) 的欧拉函数(1~m 中与 m 互质的数的个数),则:
- 若 \(gcd(a, m) = 1\)(a 与 m 互质): \(a^b \equiv a^{b \mod \varphi(m)} \mod m\)
(原本的欧拉定理,互质时无需考虑指数大小) - 若 \(gcd(a, m) ≠ 1\) 且 \(b < \varphi(m)\): $a^b \equiv a^b \mod m $
(指数较小时,直接计算,无需降幂) - 若 \(gcd(a, m) ≠ 1\) 且 \(b \ge \varphi(m)\): \(a^b \equiv a^{(b\mod \varphi(m)) + \varphi(m)} \mod m\)
(指数较大时,降幂后需加 \(φ(m)\),避免不互质导致的结果错误)
证明-oi-wiki
实在太长了,自己看吧但写的真的是能看到最好的了
使用细节-董老师视频已跳过欧拉定理
关键性质:对于 \(m ≥ 2\),\(φ(m) < m\),且 \(φ(φ(m)) < φ(m)\),即欧拉函数序列 \(m → φ(m) → φ(φ(m)) → ...\) 会快速递减至 1,最终递归终止(任何数 mod 1 都为 0)。
实现过程
由于\(mod<=1e9\),所以我们采用素数加速计算(更优的直接筛\(φ\)空间不够),我们通过递推计算每一层的\(a^b \mod m\),(在快速幂中,详见代码及注释)判断是否要加m后,将值传给下一层。最后得出答案再对m取模。
code
含较详细注释
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn = 1e6+10;int pri[maxn];
bool not_pri[maxn];
int phi[maxn];// phi[i]存计算得到的第i个phi
int wi[maxn];/**
扩展欧拉定理:
1: a^b = a^(b % phi[m]) mod m ---gcd(a,m)=1
2: a^b = a^b mod m ---gcd(a,m)!=1 && b<phi[m]
3: a^b = a^(b % phi[m] + phi[m]) mod m ---b>=phi[m] 可以包含1
证明详见 oi-wiki
*/void sieve(int n)// 线性质数筛,加速单个phi的求解速度
{not_pri[1]=1;for(int i=2;i<=n;++i){if(!not_pri[i]){pri[++pri[0]]=i;}for(int j=1;j<=pri[0] && i*pri[j]<=n;++j){not_pri[i*pri[j]]=1;if(!(i%pri[j])){break;}}}
}int get_phi(int n)// 求单个phi
{int ret=n;// 初始化为自己for(int i=1;i<=pri[0] && pri[i]*pri[i]<=n ;++i)// 若有大于sqrt(n)的质因数,最多只有1个,后续单独处理{if(!(n%pri[i])){ret=ret/pri[i]*(pri[i]-1);// 等价于*(1-1/pri[i])while(!(n%pri[i]))// 避免后续重复处理{n/=pri[i];}}}if(n!=1){ret=ret/n*(n-1);//剩余的n是一个大于sqrt(原n)的质因数}return ret;
}int fa_pow(int base,int ci,int mod)// 扩展欧拉函数的快速幂
{/*这里的base是某一层的底数a,ci(次)是指数(处理后的)b,mod是m我们在做快速幂的时候要判断:是否要对当前的返回值(下一层的b)要加m(下一层m的phi[m])*/int ret=1;while(ci){if(ci&1){ret*=base;if(ret>=mod){ret=ret%mod+mod;// 超过后取模并加m}}base*=base;if(base>=mod){base=base%mod+mod;// 可能还未更新到ret,要保留当前状态,保证下一次更新ret时能打上标记}ci>>=1;}return ret;
}int solve(int l,int r,int cnt)
{/*三个递归出口,1.石块用完了--l==r2.phi[cnt]=1,后面不管再有多少指数,取模后的结果都相同3.石子本身为1,不管多少指数都是他本身*/if(!phi[cnt+1]){phi[cnt+1]=get_phi(phi[cnt]);}if(phi[cnt]==1){return 1;// 约等于取模后加phi[cnt]}if(wi[l]==1){return 1;}if(l==r)// 最后一层,判断是那种情况{if(wi[r]>=phi[cnt]){return wi[r]%phi[cnt]+phi[cnt];}else{return wi[r];}}return fa_pow(wi[l],solve(l+1,r,cnt+1),phi[cnt]);// a^b mod m
}void read(int &x)// 本题卡常
{int f=1;x=0;char c=getchar();while(c<'0'||c>'9'){if(c=='-'){f=-1;}c=getchar();}while(c>='0' && c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}x*=f;
}signed main()
{sieve(maxn);int n,m,q;read(n);read(m);for(int i=1;i<=n;++i){read(wi[i]);}read(q);int l,r;phi[1]=m;//数组第一个存它自己for(int i=1;i<=q;++i){read(l);read(r);printf("%lld\n",solve(l,r,1)%m);//结果可能有+m表示使用第三条式子,所以要取模}return 0;
}
有问题欢迎讨论