要义:
此题为一题多解的题目,旨在拓宽我们的思维。
题目:
思路:
线段树解法:
注意到取模运算的性质:
x
x
x
m
o
d
mod
mod
p
p
p
<
<
<
x
2
{x\over 2}
2x
(
p
<
x
)
(p<x)
(p<x)
所以取模也最多是
log
x
\log x
logx 次,我们不妨记录区间最大值,如果最大值
<
p
<p
<p 直接返回,就可以通过此题。
代码:
#include<iostream>
#include<cstdio>
#define rgt register int
#define ll long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
if(!isdigit(ch)){if(ch==45)f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
inline void print(ll x){
if(!x){
putchar('0');
return;
}
ll num[22],siz=0,px=x;
while(px){
siz++;
num[siz]=px%10;
px/=10;
}
while(siz){
putchar(num[siz]+'0');
siz--;
}
}
const int mxn = 1e5+5;
int n,m,a[mxn];
struct tree{
int l;
int r;
ll sum; //区间和
ll mx; //区间最大值
}tr[mxn*4];
inline void update(int p){ //节点更新
tr[p].sum=tr[p*2].sum+tr[p*2+1].sum;
tr[p].mx=max(tr[p*2].mx,tr[p*2+1].mx);
}
void build(int dl,int dr,int p){
tr[p].l=dl;
tr[p].r=dr;
if(dl==dr){
tr[p].sum=tr[p].mx=a[dl];
return;
}
int md=(dl+dr)/2;
build(dl,md,p*2);
build(md+1,dr,p*2+1);
update(p);
}
void dchange(int dl,int dr,int p,ll x){ //单点修改
if(dl==tr[p].l&&dr==tr[p].r){
tr[p].sum=x;
tr[p].mx=x;
return;
}
int md=(tr[p].l+tr[p].r)/2;
if(dl<=md) dchange(dl,dr,p*2,x);
if(md<dr) dchange(dl,dr,p*2+1,x);
update(p);
}
void modchange(int dl,int dr,int p,ll mod){ //区间取模
if(dl<=tr[p].l&&dr>=tr[p].r&&tr[p].mx<mod)
return; //模数大于区间最大值,可以直接退出
if(dl<=tr[p].l&&dr>=tr[p].r&&tr[p].l==tr[p].r){
tr[p].mx%=mod;
tr[p].sum%=mod;
return; //单点暴力取模
}
int md=(tr[p].l+tr[p].r)/2;
if(dl<=md) modchange(dl,dr,p*2,mod);
if(md<dr) modchange(dl,dr,p*2+1,mod);
update(p);
}
ll answer(int dl,int dr,int p){ //查询区间和
if(dl<=tr[p].l&&dr>=tr[p].r)
return tr[p].sum;
int md=(tr[p].l+tr[p].r)/2;
ll ans=0;
if(dl<=md) ans+=answer(dl,dr,p*2);
if(md<dr) ans+=answer(dl,dr,p*2+1);
return ans;
}
int main(){
n=read();
m=read();
for(rgt i=1;i<=n;i++)
a[i]=read();
build(1,n,1);
for(rgt op,tl,tr,x,i=1;i<=m;i++){
op=read();
switch(op){
case 1:
tl=read();
tr=read();
print(answer(tl,tr,1));
putchar('\n');
break;
case 2:
tl=read();
tr=read();
x=read();
modchange(tl,tr,1,x);
break;
case 3:
tl=tr=read();
x=read();
dchange(tl,tr,1,x);
break;
}
}
return 0;
}
分块解法:
暴力,要开O2。
线段树与树状数组、分块常常可以互换,但本题显然不可以用树状数组。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
int a[1000001];
int l[1005],r[1005],bel[1000005];
ll sum[1005];
int mx[1005];
inline ll query(int L,int R){
int i;
ll ans=0;
if(bel[L]==bel[R]){
for(i=L;i<=R;++i)ans+=a[i];
}else{
for(i=L;i<=r[bel[L]];++i)ans+=a[i];
for(i=l[bel[R]];i<=R;++i)ans+=a[i];
for(i=bel[L]+1;i<=bel[R]-1;++i)ans+=sum[i];
}
return ans;
}
inline void change(int x,int v){
sum[bel[x]]-=a[x],sum[bel[x]]+=v;
a[x]=v;
int i;
mx[bel[x]]=0;
for(i=l[bel[x]];i<=r[bel[x]];++i)mx[bel[x]]=max(mx[bel[x]],a[i]);
}
inline void reset(int L,int R,int p){
for(int i=L;i<=R;i++)a[i]%=p;
mx[bel[L]]=0,sum[bel[L]]=0;
for(int i=l[bel[L]];i<=r[bel[L]];i++)mx[bel[L]]=max(mx[bel[L]],a[i]),sum[bel[L]]+=a[i];
}
inline void modifly(int L,int R,int p){
int i;
if(bel[L]==bel[R]){
reset(L,R,p);
}else{
reset(L,r[bel[L]],p);
reset(l[bel[R]],R,p);
for(i=bel[L]+1;i<=bel[R]-1;++i){
if(mx[i]>=p)
reset(l[i],r[i],p);
}
}
}
int main(){;
register int i,j;
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i)scanf("%d",&a[i]);
int cnt=sqrt(n);
for(i=1;i<=cnt;i++){
l[i]=r[i-1]+1;
r[i]=l[i]+cnt-1;
}
if(r[cnt]<n){
cnt++;
l[cnt]=r[cnt-1]+1;
r[cnt]=n;
}
for(i=1;i<=cnt;++i){
for(j=l[i];j<=r[i];++j){
bel[j]=i;
sum[i]+=a[j];
mx[i]=max(mx[i],a[j]);
}
}
while(m--){
int opt;
scanf("%d",&opt);
if(opt==1){//query
int l,r;
scanf("%d%d",&l,&r);
printf("%lld\n",query(l,r));
}
if(opt==2){//modifly
int l,r,p;
scanf("%d%d%d",&l,&r,&p);
modifly(l,r,p);
}
if(opt==3){//change
int x,v;
scanf("%d%d",&x,&v);
change(x,v);
}
}
return 0;
}
小结:一题多解在学习阶段十分重要。