题意
给定长度为 \(n\) 的数列 \(a\),要求支持 \(m\) 次以下操作:
- 将区间 \([l,r]\) 的每一个 \(a_i\) 替换为 \(c^{a_i}\),其中 \(c\) 是给定的常数
- 询问 \([l,r]\) 的区间和,对 \(p\) 取模
\(1 \leq n,m \leq 5 \times 10^4,1 \leq p \leq 10^8,0 \leq a_i < p,0 < c < p\)
题解
观察到结果要对 \(p\) 取模。考虑 \(a_i\) 进行若干次操作后,变为了
\[c^{c^{{\cdots^{a_i}}}} \mod p \]根据扩展欧拉定理,在 \(c^{\cdots^{a_i}}\) 大于 \(\varphi(p)\) 的情况下,上式等于
\[c^{c^{{\cdots^{a_i}}} \mod \varphi(p) + \varphi(p)} \mod p \]而 \(c^{\cdots^{a_i}} \mod \varphi(p)\) 在指数大于 \(\varphi(\varphi(p))\) 的情况下又等于
\[c^{\cdots^{a_i} \mod \varphi(\varphi(p)) + \varphi(\varphi(p))} \mod \varphi(p) \]这样递归下去,必定会出现指数小于某个 \(\varphi\) 函数的情况,或者那一层对应的 \(\varphi\) 函数为 \(1\)。观察到,如果递归到某一层时,\(\varphi\) 函数为 \(1\),那么接下来的所有操作都是不会影响答案 —— 那一层的指数必定是 \(1\)。
我们可以证明当某一层对应的 \(\varphi\) 函数值为 \(1\) 时,递归层数不超过 \(O(\log p)\) 层。当某一层的 \(\varphi\) 函数值为 \(x\) 时,如果 \(x\) 是偶数,则 \(\varphi(x) \leq \dfrac{1}{2}x\)。如果 \(x\) 是奇数,根据 \(\varphi\) 的计算式,\(\varphi(x)\) 是偶数。那么经过 \(O(\log p)\) 层递归后,\(\varphi(x)\) 一定为 \(1\)。
于是我们可以记录下递归层数 \(c\) 并暴力修改。如果某个区间修改次数的最小值已经大于 \(c\),那么我们就不需要修改了(类似于区间开根那道题的思路)。
算上快速幂的复杂度,这样做是 \(O(n \log ^2n \log p)\) 的。我们需要预处理幂次(类似于 BSGS 算法,预处理 \(c^{0...10^4}\) 和 \(c^{10^4},c^{2\times 10^4},...,c^{10^4\times 10^4}\),每次询问按照把两边乘起来),这样单次修改就可以降到 \(O(\log ^2n)\),总复杂度 \(O(n \log ^2 n)\)。
# include <bits/stdc++.h>
# define int long long
const int N=100010,SQRT=10005,MAXX=10000,INF=0x3f3f3f3f;
struct Node{
int sum;
int minx;
}tree[N<<2];
int bpow[SQRT][40],gpow[SQRT][40];
bool overb[SQRT][40],overg[SQRT][40];
int n,m,MOD,c;
int p[N],pcnt;
int a[N];
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-')f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
inline int getphi(int x){ // 计算 phi
int temp=x,phi=x;
for(int i=2;i*i<=temp;++i){
if(temp%i==0){
phi-=phi/i;
while(temp%i==0)
temp/=i;
}
}
if(temp>1)
phi-=phi/temp;
return phi;
}
inline void init_pow(void){ // 计算 c 的幂次 0..10^4
for(int i=0;i<=pcnt;++i){ // 表示是第 i 层递归的模数
bpow[0][i]=1;
for(int j=1;j<=MAXX;++j){
bpow[j][i]=bpow[j-1][i]*c;
if(bpow[j][i]>=p[i]){ // 记录下,已经大于模数,这样就可以使用扩展欧拉定理
overb[j][i]=true,bpow[j][i]%=p[i];
}
overb[j][i]|=overb[j-1][i]; // 如果前一个幂就大于了当然也行
}
}
for(int i=0;i<=pcnt;++i){ // 计算 c 的幂次 10^4...10^8
gpow[0][i]=1;
for(int j=1;j<=MAXX;++j){
gpow[j][i]=gpow[j-1][i]*bpow[MAXX][i];
if(gpow[j][i]>=p[i]){
overg[j][i]=true,gpow[j][i]%=p[i];
}
overg[j][i]|=overg[j-1][i];
}
}
return;
}
inline std::pair <int,bool> qpow(int val,int i){ // 计算 c^val \mod p[i]
int bstep=val%MAXX,gstep=val/MAXX,res=bpow[bstep][i]*gpow[gstep][i];
bool f=overb[bstep][i]||overg[gstep][i];
if(res>=p[i]){ // 扩展欧拉定理
f=true,res%=p[i];
}
return std::make_pair(res,f);
}
inline int calc(int val,int i){
int now=val;
if(now>=p[i]) // 扩展欧拉定理
now=now%p[i]+p[i];
for(;i;--i){ // 递归计算
std::pair <int,bool> res=qpow(now,i-1); // 上一层的答案作为这一层的指数
now=res.first;
if(res.second)
now+=p[i-1];
}
return now;
}
inline int lc(int x){
return x<<1;
}
inline int rc(int x){
return x<<1|1;
}
inline void pushup(int k){
tree[k].minx=std::min(tree[lc(k)].minx,tree[rc(k)].minx);
tree[k].sum=(tree[lc(k)].sum+tree[rc(k)].sum)%MOD;
}
void build(int k,int l,int r){
if(l==r){
tree[k].sum=a[l];
return;
}
int mid=(l+r)>>1;
build(lc(k),l,mid),build(rc(k),mid+1,r);
pushup(k);
return;
}
void change(int k,int l,int r,int L,int R){
if(tree[k].minx>=pcnt)
return;
if(l==r){
++tree[k].minx,tree[k].sum=calc(a[l],tree[k].minx);
return;
}
int mid=(l+r)>>1;
if(L<=mid)
change(lc(k),l,mid,L,R);
if(mid<R)
change(rc(k),mid+1,r,L,R);
pushup(k);
return;
}
int query(int k,int l,int r,int L,int R){
if(L<=l&&r<=R){
return tree[k].sum;
}
int mid=(l+r)>>1,res=0;
if(L<=mid)
res+=query(lc(k),l,mid,L,R);
if(mid<R)
res=(res+query(rc(k),mid+1,r,L,R))%MOD;
return res;
}
# undef int
int main(void){
# define int long long
n=read(),m=read(),MOD=read(),c=read();
for(int i=1;i<=n;++i){
a[i]=read();
}
p[0]=MOD;
while(p[pcnt]>1)
++pcnt,p[pcnt]=getphi(p[pcnt-1]);
p[++pcnt]=1; // 因为线段树里用了 >= 于是要多一个
// 当然也可以无视这一句,然后把线段树里 tree[k].minx>=pcnt 改为 >
init_pow();
build(1,1,n);
int opt,l,r;
while(m--){
opt=read(),l=read(),r=read();
if(!opt){
change(1,1,n,l,r);
}else{
printf("%lld\n",query(1,1,n,l,r)%MOD);
}
}
return 0;
}