[题解]CF438D

要义:

此题为一题多解的题目,旨在拓宽我们的思维。

题目:

CF438D

思路:

线段树解法:

注意到取模运算的性质:
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;
}

小结:一题多解在学习阶段十分重要。
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值