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

题意

给定长度为 (n) 的数列 (a),要求支持 (m) 次以下操作:

  • 将区间 ([l,r]) 的每一个 (a_i) 替换为 (c^{a_i}),其中 (c) 是给定的常数
  • 询问 ([l,r]) 的区间和,对 (p) 取模

(1 leq n,m leq 5 imes 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 imes 10^4},...,c^{10^4 imes 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
",query(1,1,n,l,r)%MOD);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/liuzongxin/p/15256827.html