幂塔问题-扩展欧拉函数
嵌套幂模运算问题(基于扩展欧拉定理)
问题背景
给定一个数组 \(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==r
2.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;
}
有问题欢迎讨论

浙公网安备 33010602011771号