幂塔问题-扩展欧拉函数

嵌套幂模运算问题(基于扩展欧拉定理)


问题背景

给定一个数组 \(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 互质的数的个数),则:

  1. \(gcd(a, m) = 1\)(a 与 m 互质): \(a^b \equiv a^{b \mod \varphi(m)} \mod m\)
    (原本的欧拉定理,互质时无需考虑指数大小)
  2. \(gcd(a, m) ≠ 1\)\(b < \varphi(m)\) $a^b \equiv a^b \mod m $
    (指数较小时,直接计算,无需降幂)
  3. \(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;
}

有问题欢迎讨论

posted @ 2025-11-10 15:18  玖玮  阅读(0)  评论(0)    收藏  举报