BZOJ 3585 mex 莫队算法+分块

13 篇文章 0 订阅
3 篇文章 1 订阅

BZOJ 3585 mex

题目链接:右转进入题目

题目大意:给定n,m,n个数(0<=ai<=1e9),m个询问;

每次询问一段区间[L,R],在aL,a(L+1),,,,aR中第一个没有出现的自然数是多少?

n,m<=200010


题解:

裸的莫队算法。

但是这个题有一些值得注意的地方:

第零,由于莫队算法有nsqrt(n)次修改,m次询问,n和m同阶。

可以看到修改次数比询问次数多得多。

辣么说如果用线段树的话,修改和询问都是O(lng)的,那么复杂度就是O(n*sqrt(n)*lgn)的,会TLE。

所以改用分块。尽管询问是O(sqrt(n))的,但是修改是O(1)的。

因此改用分块就变成了O(n*sqrt(n))。就不会TLE了。

分块的做法是从第一块开始扫,扫到第一块不是每个点都是1的块,在块内暴力检查。

复杂度显然是O(sqrt(n))的。

第一,虽然ai<=1e9,但是没有离散化的必要。更准确的说,对于>n的数字可以忽视。

因为>n的数字一定不会成为答案(想一想,为什么)。

一开始我还想离散化发现怎么也想不对,看了大神的blog才明白【捂脸

第二,绝大多数人下意识的当成了正整数,所以分块的时候就没有考虑0的情况。

但事实上有两种解决方法不需要重写,要么求mex的时候直接看一下a[0]的值,要么把读入的所有n个数字+1,最后查询得到的答案-1即可。

代码里用的前者。

附上代码:

//BZOJ 3585
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#define MAXN 200010
#define MAXM 200010
#define BLOCK_CNT 500
using namespace std;
int n,m,cnt[MAXN],a[MAXN];
struct block{
	private:
		int val[MAXN],belong[MAXN],sz,cnt,n;
		int L[BLOCK_CNT],R[BLOCK_CNT],sumn[BLOCK_CNT];
	public:
		void init(int _n)
		{
			sz=sqrt(_n+0.5);
			n=_n;cnt=n/sz+1;
			belong[0]=0;
			for(int i=1;i<=n;i++)
				belong[i]=(i-1)/sz+1;
			for(int i=1;i<=cnt;i++)
				L[i]=(i-1)*sz+1,R[i]=i*sz;
		}
		void update(int pos,int v)
		{
			sumn[belong[pos]]+=v;
			val[pos]+=v;
		}
		int mex()
		{
			if(val[0]==0) return 0;
			int cur=1;
			while(cur<=cnt&&sumn[cur]==sz) cur++;
			for(int i=L[cur];i<=R[cur];i++)
				if(val[i]==0) return i;
			return 0;
		}
}b;
struct query{
	int l,r,id,pos;
}q[MAXM];
bool cmp(const query &q1,const query &q2)
{
	if(q1.pos==q2.pos) return q1.r<q2.r;
	else return q1.pos<q2.pos;
}
int ans[MAXM];
void add(int x)
{
	if(a[x]>=n) return;
	if(cnt[a[x]]==0) b.update(a[x],1);
	cnt[a[x]]++;
}
void del(int x)
{
	if(a[x]>=n) return;
	cnt[a[x]]--;
	if(cnt[a[x]]==0) b.update(a[x],-1);
}
int main()
{
	scanf("%d%d",&n,&m);int sz;
	sz=sqrt(n+0.5);b.init(n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;q[i].pos=q[i].l/sz;
	}
	sort(q+1,q+m+1,cmp);
	int L=1,R=0;
	for(int i=1;i<=m;i++)
	{
		while(L<q[i].l) del(L++);
		while(L>q[i].l) add(--L);
		while(R<q[i].r) add(++R);
		while(R>q[i].r) del(R--);
		ans[q[i].id]=b.mex();
	}
	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、付费专栏及课程。

余额充值