XSY3938 平方问题

题目大意

有一个长度为 n n n 的序列,支持以下两种操作:

  • 将区间内的每个数分别平方
  • 查询区间和对 998244353 取模的结果

n ≤ 2 × 1 0 5 n≤2\times 10^5 n2×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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值