【Luogu】 P4137 Rmq Problem / mex

文章介绍了两种处理区间修改和查询问题的算法:一种是基于回滚操作的莫队算法,适用于解决区间加法修改的问题,时间复杂度为O(Nsqrt(N));另一种是使用可持久化线段树,通过维护最大位置信息进行二分查找,适用于更复杂的查询需求。
摘要由CSDN通过智能技术生成

题目链接

点击打开链接

题目解法

观察样例可知自然数包括0(废话)

算法1

这道题可以用回滚莫队做

考虑时间复杂度

可以想到答案一定不超过区间的长度

那么块内加数:暴力修改答案每次需要O(\sqrt{n})

跨块加数:因为有\sqrt{n}个块,每个块中右端点从小到大,答案必定只加不减,所以每一块是O(n),时间复杂度是O(n\sqrt{n})

所以回滚莫队的时间复杂度是O(n\sqrt{n})

#include <bits/stdc++.h>
using namespace std;
const int N(200100),M(200100),S(200100);
struct Query{
	int id,l,r;
}query[M];
int n,m,a[N],pos[N],cnt[S],ans[M];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar())
		if(ch=='-')
			RR=-1;
	for(;isdigit(ch);ch=getchar())
		FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
bool cmp(const Query &x,const Query &y){
	return pos[x.l]^pos[y.l]?pos[x.l]<pos[y.l]:x.r<y.r;
}
void add(int x,int &res){
	cnt[x]++;
	while(cnt[res]&&cnt[res+1]&&cnt[res+2])
		res+=3;
	while(cnt[res])
		res++;
}
int main(){
	n=read(),m=read();
	int B=sqrt(n);
	for(int i=1;i<=n;i++)
		a[i]=read(),pos[i]=(i-1)/B+1;
	for(int i=1,l,r;i<=m;i++)
		l=read(),r=read(),query[i]={i,l,r};
	sort(query+1,query+m+1,cmp);
	for(int k=1;k<=m;){
		int b=pos[query[k].l];
		int _r=b*B,i=_r+1,j=_r;
		int res=0;
		for(;pos[query[k].l]==b;k++){
			int id=query[k].id,l=query[k].l,r=query[k].r;
			if(pos[query[k].r]==b){
				for(int i=l;i<=r;i++)
					add(a[i],res);
				ans[id]=res;
				res=0;
				for(int i=l;i<=r;i++)
					cnt[a[i]]=0;
			}
			else{
				while(j<r)
					add(a[++j],res);
				int bres=res;
				while(i>l)
					add(a[--i],res);
				ans[id]=res;
				res=bres;
				while(i<=_r)
					cnt[a[i++]]--;
			}
		}
		memset(cnt,0,sizeof(cnt));
	}
	for(int i=1;i<=m;i++)
		printf("%d\n",ans[i]);
	return 0;
}

这道题因为比较卡常,所以莫队只能拿到只能90分

算法2

看到没有出现的几个字会联想到SDOI2009 HH的项链

于是我们考虑用可持久化线段树来做

但是一个数是否出现过不好用线段树节点之间的加减来维护

需要考虑维护其他信息达到此效果

可持久化线段树常见的套路是记录权值相同的数出现的最大位置pos

我们这道题也可以用这个套路

如果区间[l,r]中有权值的pos<l,那么这个权值一定没有在[l,r]中出现过

所以这启发我们在可持久化线段树上二分完成这个问题,离线下来可以不用可持久化

时间复杂度O(n logn)

#include <bits/stdc++.h>
using namespace std;
const int N(200100);
struct SGT{
	int lc,rc,mini;
}seg[N*4+N*20];
int n,m;
int root[N],idx;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar())
		if(ch=='-')
			RR=-1;
	for(;isdigit(ch);ch=getchar())
		FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
int build(int l,int r){
	int p=++idx;
	if(l==r)
		return p;
	int mid=(l+r)>>1;
	seg[p].lc=build(l,mid),seg[p].rc=build(mid+1,r);
	return p; 
}
int insert(int p,int l,int r,int pos,int k){
	int q=++idx;
	seg[q]=seg[p];
	if(l==r){
		seg[q].mini=k;
		return q;
	}
	int mid=(l+r)>>1;
	if(mid>=pos)
		seg[q].lc=insert(seg[p].lc,l,mid,pos,k);
	else
		seg[q].rc=insert(seg[q].rc,mid+1,r,pos,k);
	seg[q].mini=min(seg[seg[q].lc].mini,seg[seg[q].rc].mini);
	return q;
}
int query(int p,int l,int r,int k){
	if(l==r)
		return l;
	int mid=(l+r)>>1;
	if(seg[seg[p].lc].mini<k)
		return query(seg[p].lc,l,mid,k);
	else
		return query(seg[p].rc,mid+1,r,k);
}
int main(){
	n=read(),m=read();
	root[0]=build(0,N-1);
	for(int i=1,x;i<=n;i++)
		x=read(),root[i]=insert(root[i-1],0,N-1,x,i);
	for(int i=1,l,r;i<=m;i++){
		l=read(),r=read();
		printf("%d\n",query(root[r],0,N-1,l));
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值