【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;
}