[ZOJ]ZOJ3998(线段树,费马小定理)
题意:询问数列中一段的乘积,支持区间乘法和区间乘方
基本类似于支持区间加法和区间乘法的区间求和的线段树,对两种操作打两个tag,根据运算规则pushdown维护好标记即可。
乘方的取模利用费马小定理的推论:a^n ≡ a^ (n mod φ(p))(modp),p为质数时φ(p)=p-1
#include<bits/stdc++.h> using namespace std; #define LL long long #define rep(i,n) for(int i=1;i<=n;i++) const int N=1e5+7; const int mod=1e9+7; int n,a[N],tree[N*4],tag1[N*4],tag2[N*4],len[N*4]; inline void update(int node){ tree[node]=1LL*tree[node<<1]*tree[node<<1|1]%mod; } inline int po(int x,int y){ int res=1; while(y){ if(y&1)res=1LL*res*x%mod; y>>=1; x=1LL*x*x%mod; } return res; } inline void maketag(int node,int v,int k){ tree[node]=1LL*po(tree[node],k)*po(v,len[node])%mod; tag1[node]=1LL*po(tag1[node],k)*v%mod; tag2[node]=1LL*tag2[node]*k%(mod-1); } inline void push_down(int node){ maketag(node<<1,tag1[node],tag2[node]); maketag(node<<1|1,tag1[node],tag2[node]); tag1[node]=tag2[node]=1; } inline int query(int node,int L,int R,int l,int r){ if(l>R||r<L)return 1; if(L>=l&&R<=r)return tree[node]; push_down(node); int mid=(L+R)>>1; return 1LL*query(node<<1,L,mid,l,r)*query(node<<1|1,mid+1,R,l,r)%mod; } inline void change(int node,int L,int R,int l,int r,int v,int k){ if(l>R||r<L)return; if(L>=l&&R<=r){ maketag(node,v,k); return ; } if(tag1[node]!=1||tag2[node]!=1)push_down(node); int mid=(L+R)>>1; change(node<<1,L,mid,l,r,v,k); change(node<<1|1,mid+1,R,l,r,v,k); update(node); } void build(int node,int l,int r){ tag1[node]=tag2[node]=1; len[node]=r-l+1; if(l==r){ tree[node]=a[l]; return; } int mid=(l+r)>>1; build(node<<1,l,mid); build(node<<1|1,mid+1,r); update(node); } int main() { int t,q; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&q); rep(i,n)scanf("%d",&a[i]); build(1,1,n); while(q--){ int op,l,r,v; scanf("%d%d%d",&op,&l,&r); if(op==1){ scanf("%d",&v); change(1,1,n,l,r,v,1); } if(op==2){ scanf("%d",&v); change(1,1,n,l,r,1,v); } if(op==3){ printf("%d\n",query(1,1,n,l,r)); } } } //system("pause"); }