把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ4869】[SHOI2017] 相逢是问候(扩展欧拉定理+线段树)

点此看题面

大致题意: 给定\(n,c,p\)以及一个长度为\(n\)的数组\(a_i\),要求支持两种操作:把一段区间内的\(a_i\)全修改为\(c^{a_i}(mod\ p)\);区间求和。

前言

这道题口胡起来其实并不难,但写起来细节巨多。

关于扩展欧拉定理,我觉得可以先去做做这道题:【BZOJ3884】上帝与集合的正确用法(扩展欧拉定理)

大致思路

考虑根据上面那道题的思想,对于\(c^{c^{c^{...}}}(mod\ p)\),在\(c\)的层数足够多,\(\phi(p)=1\)时,无论再如何修改都不会影响它的值了。

也就是说,对于一段已经完成修改的区间,每当修改到它的时候,都可以直接\(return\),这极大优化了复杂度。

否则,层数只是\(O(log\ p)\)级别的,我们可以暴力跳每一层处理答案。

注意这里关于扩展欧拉定理的应用有一个非常坑的地方,\(a^b\equiv a^{b\ mod\ \phi(p)+\phi(p)}(mod\ p)\)是在\(b\ge\phi(p)\)时才成立的。(于是就为了判断\(b\)是否大于等于\(\phi(p)\)我的码量直接增长了三分之一)

至于具体如何判断,我们只需要在快速幂的同时记录\(b\)是否向\(\phi(p)\)取模过即可。

然而为了优化(毕竟这个复杂度可是三个\(log\)的),我们需要给快速幂打表,即对于每个模数\(p_x\)分别预处理出\(p_x^{10^4\times i}\)\(p_x^{i}\),然后我们还要开两个数组\(f1_{x,i}\)\(f2_{x,i}\)来记录每个幂是否向\(\phi(p)\)取过模。

算了算了,反正这种东西讲也很难讲清楚,我讲起来也挺烦的。我觉得在理解大致思路的基础上,还不如把代码贴出来,结合上注释,应该会更容易理解。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define SX 10000
using namespace std;
int n,c,X,a[N+5],k,p[N+5],f1[100][SX+5],pw1[100][SX+5],f2[100][SX+5],pw2[100][SX+5],fg;
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
		Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
I void Init(RI x)//预处理
{
	RI i;for(pw2[k][0]=i=1;i<=SX;++i)//预处理1~10000的幂
		pw2[k][i]=1LL*pw2[k][i-1]*c%p[k],
		f2[k][i]=f2[k][i-1]||(1LL*pw2[k][i-1]*c>=p[k]);//从上一个继承,或如果当前取模了,则f=1
	for(pw1[k][0]=i=1;i<=SX;++i)//预处理(1~10000)×10000的幂
		pw1[k][i]=1LL*pw1[k][i-1]*pw2[k][SX]%p[k],
		f1[k][i]=f2[k][SX]||f1[k][i-1]||(1LL*pw1[k][i-1]*pw2[k][SX]>=p[k]);//这里还要从10000的幂处继承
	if(x==1)//x=1是边界
	{
		for(p[++k]=1,i=1;i<=SX;++i)//注意这里要复制一遍,如果你洛谷上WA在第3和第20个点,就是漏了它
			f1[k][i]=f1[k-1][i],pw1[k][i]=pw1[k-1][i],
			f2[k][i]=f2[k-1][i],pw2[k][i]=pw2[k-1][i];
		return;
	}
	RI t=1;for(i=2;i*i<=x;++i) if(!(x%i)) {t*=i-1,x/=i;W(!(x%i)) t*=i,x/=i;}//求φ
	x^1&&(t*=x-1),Init(p[++k]=t);//递归继续处理
}
I int QP(CI i,CI y)//快速幂
{
	RI a=y/SX,b=y%SX,t=1LL*pw1[i][a]*pw2[i][b]%p[i];//根据打表出的结果计算
	return fg=f1[i][a]|f2[i][b]|(1LL*pw1[i][a]*pw2[i][b]>=p[i]),t;//fg记录是否取模过
}
class SegmentTree//线段树
{
	private:
		#define PT CI l=1,CI r=n,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		#define PU(x) (T[x]=min(T[x<<1],T[x<<1|1]),S[x]=(S[x<<1]+S[x<<1|1])%X)
		int V[N<<2],S[N<<2],T[N<<2];
		I int GV(CI d,CI Mx,CI v)//递归求值
		{
			if(d==Mx) return fg=v>=p[d],v%p[d];return QP(d,GV(d+1,Mx,v)+fg*p[d+1]);//边界用v求值,否则快速幂
		}
	public:
		I void Build(int *a,PT)//建树
		{
			if(l==r) return (void)(V[rt]=S[rt]=a[l],T[rt]=0);int mid=l+r>>1;
			Build(a,LT),Build(a,RT),PU(rt); 
		}
		I void U(CI x,CI y,PT)//区间修改
		{
			if(T[rt]==k) return;if(l==r) return (void)(S[rt]=GV(0,++T[rt],V[rt]));//完成修改的区间直接return,单点暴力修改
			int mid=l+r>>1;x<=mid&&(U(x,y,LT),0),y>mid&&(U(x,y,RT),0),PU(rt);//递归修改
		}
		I int Q(CI x,CI y,PT)//十分正常的区间求和
		{
			if(x==l&&r==y) return S[rt];int mid=l+r>>1;
			if(y<=mid) return Q(x,y,LT);if(x>mid) return Q(x,y,RT);
			return (Q(x,mid,LT)+Q(mid+1,y,RT))%X;
		}
}S;
int main()
{
	RI Qt,op,x,y;F.read(n),F.read(Qt),F.read(X),F.read(c),Init(p[0]=X);
	for(RI i=1;i<=n;++i) F.read(a[i]);S.Build(a);//读入+初始化建树
	W(Qt--) F.read(op),F.read(x),F.read(y),op?F.writeln(S.Q(x,y)):S.U(x,y);//处理操作
	return F.clear(),0;
}
posted @ 2020-05-18 21:03  TheLostWeak  阅读(132)  评论(0编辑  收藏  举报