zoukankan      html  css  js  c++  java
  • 【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^bequiv a^{b mod phi(p)+phi(p)}(mod p))是在(bgephi(p))时才成立的。(于是就为了判断(b)是否大于等于(phi(p))我的码量直接增长了三分之一)

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

    然而为了优化(毕竟这个复杂度可是三个(log)的),我们需要给快速幂打表,即对于每个模数(p_x)分别预处理出(p_x^{10^4 imes 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('
    ');}
    		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;
    }
    
  • 相关阅读:
    Javascript快速入门(上篇)
    Linux快速入门01-基础概念
    正则表达式快速入门
    Sublime快速入门
    centos 6.5 git 服务器的配置(入门级)
    centos6.5 网卡的处理
    centos 6.5 u盘 安装问题 :vesamenu.c32: Not a COM32R image
    ubuntu 14.04 对exfat的支持
    [转]ubuntu 14.04 如何开启和关闭触控板
    ubuntu 下mongodb安装
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4869.html
Copyright © 2011-2022 走看看