题目大意
有一个长度为 n n n 的序列,支持以下两种操作:
- 将区间内的每个数分别平方
- 查询区间和对 998244353 取模的结果
n ≤ 2 × 1 0 5 n≤2\times 10^5 n≤2×105。
题解
赛时想破头了不会,最后决定写一个暴力,写暴力前先随便打了个表,结果发现每个数的循环节长度好像都不超过30,再仔细看看发现循环节长度的最大公倍数是24。
于是直接写一棵线段树,对于每个数存下他的一个周期,每次操作就是整体往右移一个位置。
然后我们就做完了。
至于循环节长度是24的因数的证明……我并不会,看了眼题解,大意好像是,平方相当于指数乘2,因此循环节长度的lcm为
2
x
2^x
2x 的循环节长度,具体跑一下发现是24。感性理解了一下,好似说得很有道理,但赛时想不到()反正能过就行,打表yyds。
Code
#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int N=2e5+5,K=24,mod=998244353;
int n,m,a[N];
struct segment_tree{
int h[N<<2][K+5],laz[N<<2],c[N<<2];
void pushup(int p){
c[p]=min(c[ls],c[rs]);
for(int i=0;i<K;i++)
h[p][i]=(h[ls][i]+h[rs][i])%mod;
}
void build(int p,int l,int r){
if(l==r){
h[p][0]=a[l];
for(int i=1;i<K;i++)
h[p][i]=1ll*h[p][i-1]*h[p][i-1]%mod;
return;
}
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(p);
}
void pushdown(int p){
int t[K+5];
laz[p]%=K;
if(laz[p]==0) return;
for(int i=0;i<K;i++)
t[i]=h[ls][i];
for(int i=0;i<K;i++)
h[ls][i]=t[(i+laz[p])%K];
for(int i=0;i<K;i++)
t[i]=h[rs][i];
for(int i=0;i<K;i++)
h[rs][i]=t[(i+laz[p])%K];
laz[ls]=(laz[ls]+laz[p])%K,laz[rs]=(laz[rs]+laz[p])%K;
laz[p]=0;
}
void update(int p,int l,int r,int a,int b){
if(a<=l&&b>=r&&c[p]>=K){
int t[K+5];
memcpy(t,h[p],sizeof(t));
for(int i=0;i<K;i++)
h[p][i]=t[(i+1)%K];
laz[p]++;
return;
}
if(l==r){
c[p]++;
h[p][0]=h[p][1];
for(int i=1;i<K;i++)
h[p][i]=1ll*h[p][i-1]*h[p][i-1]%mod;
return;
}
if(laz[p]) pushdown(p);
int mid=l+r>>1;
if(a<=mid) update(ls,l,mid,a,b);
if(b>mid) update(rs,mid+1,r,a,b);
pushup(p);
}
int query(int p,int l,int r,int a,int b){
if(a<=l&&b>=r) return h[p][0];
if(laz[p]) pushdown(p);
int mid=l+r>>1,res=0;
if(a<=mid) res=(res+query(ls,l,mid,a,b))%mod;
if(b>mid) res=(res+query(rs,mid+1,r,a,b))%mod;
return res;
}
}t;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
t.build(1,1,n);
for(int i=1,opt,x,y;i<=m;i++){
scanf("%d%d%d",&opt,&x,&y);
if(opt==1) t.update(1,1,n,x,y);
else printf("%d\n",t.query(1,1,n,x,y));
}
return 0;
}