题解 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^{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;
}
上一篇:多元函数微分学条件极值(拉格朗日乘数法)求解技巧总结


下一篇:数学