【BZOJ 3339】Rmq Problem

传送门


problem

在这里插入图片描述
数据范围: n , q ≤ 2 × 1 0 5 n,q\le 2\times 10^5 n,q2×105 0 ≤ a i ≤ 2 × 1 0 5 0\le a_i\le 2\times 10^5 0ai2×105


solution

这道题删点很好做,每次删点之后只有它本身可能成为 m e x mex mex,但是加点不太好做。

只减不加的回滚莫队:

  1. 对原序列进行分块,以左端点所在的块升序为第一关键字,以右端点降序为第二关键字来排序。
  2. 对于处理所有左端点在块 T T T 内的询问,我们先将莫队区间左端点初始化为 L [ T ] L[T] L[T],右端点初始化为 n n n,这是一个大区间。
  3. 对于左右端点在同一个块中的询问,我们直接暴力扫描回答即可。
  4. 对于左右端点不在同一个块中的所有询问,由于其右端点降序,从 n n n 的位置开始,我们对右端点只做删点操作,总共最多删点 n n n 次。
  5. 对于左右端点不在同一个块中的所有询问,其左端点是可能乱序的,我们每一次从 L [ T ] L[T] L[T] 的位置出发,只做删点操作,到达询问位置即可,每一个询问最多加 n \sqrt n n 次。回答完询问后,我们撤销本次移动左端点的所有改动,使左端点回到 L [ T ] L[T] L[T] 的位置。
  6. 按照相同的方式处理下一块。

时间复杂度 O ( n n ) O(n\sqrt n) O(nn )


code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
using namespace std;
int n,m,S,sum,now;
int a[N],in[N],ans[N],cnt[N],num[N];
struct Query{int l,r,id;}Q[N];
bool operator<(const Query &p,const Query &q){
	return in[p.l]==in[q.l]?p.r>q.r:p.l<q.l;
}
int init(int l,int r){
	memset(cnt,0,sizeof(cnt));
	for(int i=l;i<=r;++i)  ++cnt[a[i]];
	for(int i=0;;++i)  if(!cnt[i])  return i;
}
int Get(int l,int r){
	memset(num,0,sizeof(num));
	for(int i=l;i<=r;++i)  ++num[a[i]];
	for(int i=0;;++i)  if(!num[i])  return i;
}
void Del(int pos){
	cnt[a[pos]]--;
	if(!cnt[a[pos]])  now=min(now,a[pos]);
}
int solve(int i,int id){
	int lim=(id-1)*S+1,L=lim,R=n;
	now=init(L,R);
	for(;in[Q[i].l]==id;++i){
		if(in[Q[i].l]==in[Q[i].r]){
			ans[Q[i].id]=Get(Q[i].l,Q[i].r);
			continue;
		}
		while(R>Q[i].r)  Del(R--);
		int tmp=now;
		while(L<Q[i].l)  Del(L++);
		ans[Q[i].id]=now;
		while(L>lim)  cnt[a[--L]]++;
		now=tmp;
	}
	return i;
}
int main(){
	scanf("%d%d",&n,&m),S=sqrt(n);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]),in[i]=(i-1)/S+1;
	}
	sum=in[n];
	for(int i=1;i<=m;++i){
		scanf("%d%d",&Q[i].l,&Q[i].r),Q[i].id=i;
	}
	sort(Q+1,Q+m+1);
	for(int i=1,id=1;id<=sum;++id)  i=solve(i,id);
	for(int i=1;i<=m;++i)  printf("%d\n",ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值