题解 Luogu P3747 [六省联考 2017]相逢是问候
题意
给定长度为 \(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^{\cdots^{a_i}}\) 大于 \(\varphi(p)\) 的情况下,上式等于
而 \(c^{\cdots^{a_i}} \mod \varphi(p)\) 在指数大于 \(\varphi(\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;
}