标签:push ref ase getch etc pushd 区间 eve bool
这题我在考场上也是想出了正解的……但是没调出来。
题目链接:CF原网
题目大意:给一个长度为 $n$ 的序列 $a$,$q$ 个操作:区间乘 $x$,求区间乘积的欧拉函数模 $10^9+7$ 的值。
$1\le n\le 4\times 10^5,1\le q\le 2\times 10^5,1\le a_i,x\le 300$。时限 5.5s,空限 256MB。
明显线段树。
有一个想法是维护区间积的欧拉函数,但是这样时间复杂度和代码复杂度都很高……
我的做法是维护区间积。而欧拉函数,就是看看区间中包含什么质因子,然后除一下乘一下好了。
区间积就不用说了。
包含什么质因子?难道要开bool数组吗?时间复杂度很高……
经过后台黑科技操作发现 $300$ 以内的质数只有 $62$ 个。明摆着状压的节奏!
好的,这题做完了。细节的东西在代码中都有。
对于我的代码实现来说:(以下令 $k=62$)
建树 $O(kn)$。
合并节点 $O(1)$。
下推标记 $O(\log n)$。
区间乘 $O(\log^2 n+k)$。
查询欧拉函数 $O(\log n+k)$。
总时间复杂度应该是 $O((n+q)k+q\log^2n)$。其实跑得不慢,我跑得最慢的点是1934ms。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=400040,mod=1000000007; #define lson o<<1,l,mid #define rson o<<1|1,mid+1,r #define FOR(i,a,b) for(int i=(a);i<=(b);i++) #define ROF(i,a,b) for(int i=(a);i>=(b);i--) #define MEM(x,v) memset(x,v,sizeof(x)) inline int read(){ char ch=getchar();int x=0,f=0; while(ch<‘0‘ || ch>‘9‘) f|=ch==‘-‘,ch=getchar(); while(ch>=‘0‘ && ch<=‘9‘) x=x*10+ch-‘0‘,ch=getchar(); return f?-x:x; } int n,q,pri[66],pl,a[maxn],inv[333],f[66],tag1[maxn*4]; //tag1表示区间要乘多少 ll tag2[maxn*4]; //tag2表示区间会多出哪些质因子(也是压缩过的) //为什么要两个标记呢?不能直接对tag1分解质因子吗? //因为tag1乘几遍就会被取模,这样看起来质因子就变了。所以额外加一个tag2表示真的质因子集合。 bool vis[333]; void init(){ FOR(i,2,300){ if(!vis[i]) pri[++pl]=i; for(int j=1;j<=pl && i*pri[j]<=300;j++){ vis[i*pri[j]]=true; if(i%pri[j]==0) break; } } inv[1]=1; FOR(i,2,300) inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod; FOR(i,1,pl) f[i]=1ll*inv[pri[i]]*(pri[i]-1)%mod; //f[i]表示除以p[i],再乘上p[i]-1,便于计算欧拉函数 } inline int qpow(int a,int b){ int ans=1; for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod; return ans; } struct node{ int pro;ll has; }nd[maxn*4]; //一个线段树节点,pro是区间积,has是区间包含哪些质因子(压缩过的) void pushup(node &o,node l,node r){ //合并 o.has=l.has|r.has; //直接取或 o.pro=1ll*l.pro*r.pro%mod; } void setmult(int o,int l,int r,int x,ll y){ //对第o个节点(管辖[l,r])区间乘x,质因子多了y tag1[o]=1ll*tag1[o]*x%mod; tag2[o]|=y; nd[o].pro=1ll*nd[o].pro*qpow(x,r-l+1)%mod; //记得乘r-l+1次方 nd[o].has|=y; } void pushdown(int o,int l,int r){ //下传标记 if(!tag2[o]) return; int mid=(l+r)>>1; setmult(lson,tag1[o],tag2[o]); setmult(rson,tag1[o],tag2[o]); tag1[o]=1;tag2[o]=0; //记得tag1闲置时是1 } void build(int o,int l,int r){ tag1[o]=1;tag2[o]=0; if(l==r){ nd[o].pro=a[l]; FOR(i,1,pl) //记录质因子集合 if(a[l]%pri[i]==0) nd[o].has|=1ll<<(i-1); return; } int mid=(l+r)>>1; build(lson);build(rson); pushup(nd[o],nd[o<<1],nd[o<<1|1]); } void mult(int o,int l,int r,int ql,int qr,int x,ll y){ //外面调用时先把质因子集合弄好,会省时间 if(l>=ql && r<=qr){ setmult(o,l,r,x,y); //直接设上 return; } pushdown(o,l,r); int mid=(l+r)>>1; if(mid>=ql) mult(lson,ql,qr,x,y); if(mid<qr) mult(rson,ql,qr,x,y); pushup(nd[o],nd[o<<1],nd[o<<1|1]); } node query(int o,int l,int r,int ql,int qr){ if(l>=ql && r<=qr) return nd[o]; pushdown(o,l,r); int mid=(l+r)>>1; if(mid<ql) return query(rson,ql,qr); if(mid>=qr) return query(lson,ql,qr); node ans; pushup(ans,query(lson,ql,qr),query(rson,ql,qr)); //合并两边 return ans; } int main(){ init(); n=read();q=read(); FOR(i,1,n) a[i]=read(); build(1,1,n); FOR(i,1,q){ char op[11]; scanf("%s",op); int l=read(),r=read(); if(op[0]==‘M‘){ //乘操作 int x=read();ll y=0; FOR(i,1,pl) if(x%pri[i]==0) y|=1ll<<(i-1); //先处理质因子集合 mult(1,1,n,l,r,x,y); } else{ //求欧拉函数操作 node ans=query(1,1,n,l,r); int s=ans.pro; //区间积 FOR(i,1,pl) if(ans.has&(1ll<<(i-1))) s=1ll*s*f[i]%mod; //区间含有第i个质数,那就要除以p[i],再乘上p[i]-1 printf("%d\n",s); } } }
CF1114F Please, another Queries on Array?(线段树,数论,欧拉函数,状态压缩)
标签:push ref ase getch etc pushd 区间 eve bool
原文地址:https://www.cnblogs.com/1000Suns/p/10362224.html