peiwenjun's blog 没有知识的荒原

P3747 [六省联考 2017] 相逢是问候 题解

题目描述

给定长为 \(n\) 的数组 \(a\) 和常数 \(p,c\) ,接下来 \(m\) 次操作:

  • 0 l r\(\forall i\in[l,r]\) ,执行 \(a_i\gets c^{a_i}\)
  • 1 l r :求 \((\sum_{i=l}^ra_i)\bmod p\) 的值。

数据范围

  • \(1\le n,m\le 5\cdot 10^4,1\le p\le 10^8,0\le c\lt p,0\le a_i\lt p\)

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{512MB}\)

分析

欧拉定理推广

以下定理常用于指数化简,建议积累下来:

\(n\ge\varphi(p)\) 时, \(a^n\equiv a^{n\bmod\varphi(p)+\varphi(p)}\pmod p\)

证明:

\(p=\prod_{i=1}^kp_i^{\alpha_i}\) ,对 \(\forall 1\le i\le k\) ,设 \(a=p_i^r\cdot a'\) ,其中 \(a'\)\(p_i\) 互质,我们只需证明在模 \(p_i^{\alpha_i}\) 意义下上式成立。

\(r=0\) ,由欧拉定理即证。

\(r\gt 0\) ,因为 \(\varphi(p)\ge p_i^{\alpha_i-1}\ge 1+(\alpha_i-1)(p_i-1)\ge\alpha_i\) ,从而 \(n\ge n\bmod\varphi(p)+\varphi(p)\ge\alpha_i\) ,从而两边都是零。

势能线段树

势能线段树可以用来解决这样一个问题:维护长为 \(n\) 的数组,每个位置最多被修改 \(k\) 次,要求支持区间查询。

对线段树的每个节点记录区间内修改次数的最小值,对于修改操作,如果区间内有需要被修改的节点那么往下递归,递归到叶子修改即可,查询操作同普通线段树。

这样我们花费 \(\mathcal O(\log n)\) 的代价实现一次单点修改,从而在 \(\mathcal O(nk\log n)\) 的时间内解决这一问题。具体题目中 \(k\) 一般很小(常数或 \(\mathcal O(\log n)\) 级别),所以时间复杂度是可以接受的。

核心代码如下:

void modify(int p,int l,int r)
{
    if(f[p].mn==k||l>f[p].r||r<f[p].l) return ;///区间内没有需要修改的节点,直接返回
    if(f[p].l==f[p].r) return f[p].sum=a[f[p].l][++f[p].mn],void();///叶节点单独修改
    modify(ls,l,r),modify(rs,l,r);///否则继续向下递归
    pushup(p);///维护需要查询的信息
}
光速幂

我们知道快速幂可以在 \(\mathcal O(\log n)\) 的时间内求出 \(a^n\bmod p\) 的值,那如果 \(a\)\(p\) 都固定,有没有方法做得更快?

答案是有的,假设 \(n\) 的取值范围是 \([0,B^2]\) ,预处理 \(a,a^2,\cdots,a^B\)\(a^B,a^{2B},\cdots,a^{B^2}\) 的值,将 \(n\) 拆成 \(n\%B\)\(\lfloor\frac nB\rfloor\cdot B\) 两部分,刚好可以调用预处理的信息,再将所得结果相乘即可。

一般情况下 \(p\) 在 int 范围内,根据费马小定理可以让 \(n\lt p-1\) ,因此预处理代价 \(\mathcal O(B)=\mathcal O(\sqrt p)\) 可以接受,回答 \(\mathcal O(1)\)

void init()
{
    buc1[0]=buc2[0]=1;
    for(int i=1;i<=B;i++) buc1[i]=1ll*buc1[i-1]*a%p;
    for(int i=1;i<=B;i++) buc2[i]=1ll*buc2[i-1]*a%p;
}
int qpow(int n)
{///a^n mod p
    return 1ll*buc1[n%B]*buc2[n/B]%p;
}

回到原题,根据欧拉定理推广,如果我们想求 \(c^{c^{\cdots^{a_i}}}\bmod p\)\(j\) 层)的值,只需求 \(c^{c^{\cdots^{a_i}}}\bmod\varphi(p)\)\(j-1\) 层)的值。以此类推,每减少一层,就要对模数取一次 \(\varphi\)

注意到若 \(p\) 是奇数,则取 \(\varphi\) 后至少减一;若 \(p\) 是偶数,则取 \(\varphi\) 后至少减半,因此 \(\mathcal O(n\log n)\) 次操作后 \(p\) 会变成 \(1\) ,此时幂塔模 \(p\) 的值一定为零!

因此,假设 \(p\) 经过 \(k\) 次取 \(\varphi\) 操作后变成 \(1\) (实测 \([1,10^8]\) 范围内,当 \(p=71304257\) 时, \(k\) 取最大值 \(28\) ),那么当 \(j\gt k\) 时, \(c^{c^{\cdots^{a_i}}}\bmod p\)\(j\) 层)的值都相同。

预处理每个 \(a_i\) 经过 \(j\) 次操作后的值,剩下部分就是一个势能线段树板子!

\(\varphi(\varphi(\cdots(\varphi(p))))\)\(j\) 层)为 \(p_j\) ,注意到欧拉定理推广的适用前提是 \(n\ge\varphi(p)\) ,我们需要判断当前的指数 \(x\)\(p_j\) 的大小关系,所以多了一堆细节。

注意到 \(c\gt 1\) 时, \(x\ge p_j\Rightarrow c^x\ge p_{j-1}\) (想一想,为什么?),所以只需用一个变量 flg 表示当前状态 \(x\ge p_j\) 是否成立。为了更新 flg 标记,我们需要存储 \(c\) 的幂次原始数据(而不是取模后的数据!

我们总共需要预处理 \(nk\) 个幂塔的值,单次计算需要花费 \(k\) 的代价爬塔,如果再套一个 \(\mathcal O(\log p)\) 的快速幂就吃不消了,但是注意到底数始终为 \(c\) ,用光速幂优化即可。

时间复杂度 \(\mathcal O(nk^2+\sqrt p+nk\log n)\)

#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int B=1.5e4,maxn=5e4+5;
int c,k,m,n;
int a[maxn][30],p[30],tmp[30];
int buc1[30][B+5],buc2[30][B+5];
struct node
{
    int l,r,mn,sum;
}f[maxn<<2];
int qpow(int k,int i)
{///c^k mod p[i]
    return 1ll*buc1[i][k%B]*buc2[i][k/B]%p[i];
}
int get_phi(int x)
{
    int res=x;
    for(int i=2;i*i<=x;i++)
        if(x%i==0)
        {
            res=res/i*(i-1);
            while(x%i==0) x/=i;
        }
    if(x>1) res=res/x*(x-1);
    return res;
}
void init()
{
    while(p[k]!=1) k++,p[k]=get_phi(p[k-1]);
    p[++k]=1;///如果 p[0] 经过 k 次操作变成 1 ,那么 >k 层幂塔的值都相同, 总共要存 k+1 层的值
    for(int i=0;i<=k;i++)
    {
        buc1[i][0]=buc2[i][0]=1;
        for(int j=1;j<=B;j++) buc1[i][j]=1ll*buc1[i][j-1]*c%p[i];
        for(int j=1;j<=B;j++) buc2[i][j]=1ll*buc2[i][j-1]*buc1[i][B]%p[i];
    }
    tmp[0]=1;/// tmp 数组储存幂塔原始值,对 inf 取 min
    for(int i=1;i<=27;i++) tmp[i]=min(1ll*tmp[i-1]*c,1ll<<30);
}
int add(int x,int y)
{
    static int p=::p[0];
    return x+y>=p?x+y-p:x+y;
}
void pushup(int p)
{
    f[p].mn=min(f[ls].mn,f[rs].mn);
    f[p].sum=add(f[ls].sum,f[rs].sum);
}
void build(int p,int l,int r)
{
    f[p].l=l,f[p].r=r;
    if(l==r) return f[p].sum=a[l][0],void();
    int mid=(l+r)>>1;
    build(ls,l,mid),build(rs,mid+1,r);
    pushup(p);
}
void modify(int p,int l,int r)
{
    if(f[p].mn==k||l>f[p].r||r<f[p].l) return ;
    if(f[p].l==f[p].r) return f[p].sum=a[f[p].l][++f[p].mn],void();
    modify(ls,l,r),modify(rs,l,r);
    pushup(p);
}
int query(int p,int l,int r)
{
    if(l<=f[p].l&&f[p].r<=r) return f[p].sum;
    if(l>f[p].r||r<f[p].l) return 0;
    return add(query(ls,l,r),query(rs,l,r));
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&p[0],&c),init();
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i][0]);
        for(int j=1;j<=k;j++)
        {///a[i][j]=c^..c^a[i][0]
            if(c==1) {a[i][j]=1;continue;}///特判 c=1 的情形
            int x=a[i][0],flg=0;///flg 表示 x 是否超过 p[l]
            for(int l=j;l>=1;l--)
            {///x>=p[l] 时一定有 c^x>=p[l-1]
                if(flg) x=qpow(x+p[l],l-1);
                else flg=x>=27||tmp[x]>=p[l-1],x=qpow(x,l-1);
            }
            a[i][j]=x%p[0];
        }
    }
    build(1,1,n);
    for(int op,l,r;m--;)
    {
        scanf("%d%d%d",&op,&l,&r);
        if(op==0) modify(1,l,r);
        else printf("%d\n",query(1,l,r));
    }
    return 0;
}

posted on 2025-08-29 10:39  peiwenjun  阅读(14)  评论(0)    收藏  举报

导航