题意:
长度为n的序列,m次操作
(1,l,r)求区间[l,r]的和
(2,l,r,x)区间[l,r]对x取模
(3,x,val)a[x]修改为val
输出每次操作1的结果
区间和可能爆int
思路:
操作1和3是线段树基本操作,主要问题在操作2,如果真的对每一个数都取模,肯定tle。
正确思路类似区间开方的一题,数值如果太小就不继续递归,这样剪枝之后就不会tle了。
线段树维护区间最值,取模操作的时候,如果最值比模数x小,则不需要进行操作,直接跳过。
ps:
取模操作一旦成功,那么这个数至少会减少一半,数字不断减少最值也会不断减少。
因此多次取模成功之后数字会变得很小,然后靠最值剪枝就行了(和区间开方思想基本一致)。
code:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define ll long long
const int maxm=1e5+5;
ll a[maxm<<2];//因为是存区间和,所以要开longlong
ll ma[maxm<<2];//存最值不用longlong
int n,m;
void pushup(int node){//取最值
a[node]=a[node*2]+a[node*2+1];
ma[node]=max(ma[node*2],ma[node*2+1]);
}
void build(int l,int r,int node){//建树
if(l==r){
scanf("%lld",&a[node]);
ma[node]=a[node];
return ;
}
int mid=(l+r)/2;
build(l,mid,node*2);
build(mid+1,r,node*2+1);
pushup(node);
}
void change(int x,int val,int l,int r,int node){//单点修改
if(l==r){
ma[node]=a[node]=val;
return ;
}
int mid=(l+r)/2;
if(x<=mid){
change(x,val,l,mid,node*2);
}else{
change(x,val,mid+1,r,node*2+1);
}
pushup(node);
}
void update(int x,int st,int ed,int l,int r,int node){//区间取模
if(ma[node]<x)return ;//区间最值如果小于x,则不用继续递归
if(l==r){
ma[node]=(a[node]%=x);
return ;
}
int mid=(l+r)/2;
if(st<=mid){
update(x,st,ed,l,mid,node*2);
}
if(ed>=mid+1){
update(x,st,ed,mid+1,r,node*2+1);
}
pushup(node);
}
ll ask(int st,int ed,int l,int r,int node){//区间求和
if(st<=l&&ed>=r){
return a[node];
}
int mid=(l+r)/2;
ll ans=0;
if(st<=mid){
ans+=ask(st,ed,l,mid,node*2);
}
if(ed>=mid+1){
ans+=ask(st,ed,mid+1,r,node*2+1);
}
return ans;
}
signed main(){
scanf("%d%d",&n,&m);
build(1,n,1);
while(m--){
int d;
scanf("%d",&d);
if(d==1){//区间求和
int l,r;
scanf("%d%d",&l,&r);
ll ans=ask(l,r,1,n,1);
printf("%lld\n",ans);
}else if(d==2){//区间取模
int l,r,x;
scanf("%d%d%d",&l,&r,&x);
update(x,l,r,1,n,1);
}else{//d==3,单点修改
int x,val;
scanf("%d%d",&x,&val);
change(x,val,1,n,1);
}
}
return 0;
}