题目大意
%
维护一个长度为
n
n
n 的序列,完成
m
m
m个操作,区间取模,区间求和,单点修改。
数据范围
1
⩽
n
⩽
1
0
5
,
1
⩽
m
⩽
1
0
5
1\leqslant n\leqslant 10^5,1\leqslant m\leqslant 10^5
1⩽n⩽105,1⩽m⩽105
题解
%
考虑用线段树修改,每次区间修改时暴力修改,记录一下最大值,若小于模数直接返回。
然后你会发现,这样就可以通过本题了。为什么?
引理 当
p
<
x
p<x
p<x 时,
x
 
m
o
d
 
p
<
x
2
x\bmod p< \dfrac x2
xmodp<2x
证明 若
p
<
x
2
p<\dfrac x2
p<2x,则
x
 
m
o
d
 
p
<
p
<
x
2
x\bmod p<p< \dfrac x2
xmodp<p<2x。
若
x
2
<
p
<
x
\dfrac x2<p<x
2x<p<x,则
x
 
m
o
d
 
p
=
x
−
p
<
x
2
x\bmod p=x-p< \dfrac x2
xmodp=x−p<2x。
因此,每次取模的时,若某个位置的数小于模数,则不需要修改,若大于模数,每次至少减半,因此之多被取模
log
2
a
i
\log_2 a_i
log2ai 次,因而总时间复杂度为
T
(
n
)
=
Θ
(
n
log
2
2
n
)
T(n)=\Theta(n\log_2^2 n)
T(n)=Θ(nlog22n)
#include<bits/stdc++.h>
using namespace std;
struct tree{
int l,r;
long long d,maxx;
tree *left,*right;
tree(int tl,int tr):l(tl),r(tr){
if(tl==tr){
left=right=NULL;
d=maxx=0;
return ;
}
int mid=(l+r)>>1;
left=new tree(l,mid);
right=new tree(mid+1,r);
}
void change(int x,long long da){
if(l==r){
maxx=d=da;
return;
}
if(x<=left->r) left->change(x,da);
else right->change(x,da);
maxx=max(left->maxx,right->maxx);
d=left->d+right->d;
}
void mod(int x,int y,long long p){
if(maxx<p)//不需要继续
return;
if(l==r){//暴力取模
maxx%=p;
d%=p;
return;
}
if(y<=left->r) left->mod(x,y,p);
else if(x>=right->l) right->mod(x,y,p);
else left->mod(x,left->r,p),right->mod(right->l,y,p);
maxx=max(left->maxx,right->maxx);
d=left->d+right->d;
}
long long sum(int x,int y){
if(x==l&&r==y)
return d;
if(y<=left->r)
return left->sum(x,y);
if(x>=right->l)
return right->sum(x,y);
return left->sum(x,left->r)+right->sum(right->l,y);
}
};
int main(void){
int n,m;
scanf("%d%d",&n,&m);
tree t(1,n);
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
t.change(i,x);
}
for(int i=1,ch,l,r,x;i<=m;i++){
scanf("%d",&ch);
if(ch==1){
scanf("%d%d",&l,&r);
printf("%lld\n",t.sum(l,r));
}else if(ch==2){
scanf("%d%d%d",&l,&r,&x);
t.mod(l,r,x);
}else{
scanf("%d%d",&l,&x);
t.change(l,x);
}
}
return 0;
}