RMQ / mex

题意

给出一个长为\(n\)的序列,有\(m\)个询问,每次询问\([l,r]\)区间内第一个没出现过的最小自然数(即求这个区间的\(mex\)


解法

\(\tt {CCPC}\)网络同步赛的第二题\(\tt{array}\)很像

同样也是对权值建树,每个权值保存一个位置

但不同的是那一道题保证了它是一个排列,也就是\([1,n]\)每个数最多出现一次且一定会出现一次

而这道题并没有保证这是一个排列

所以我们考虑使用主席树,对每个前缀区间建树

而此时每个位置上保存的是该数最后一次出现的位置

这样的好处是显而易见的,如果对于\([1,R]\)这个前缀,我们找到的一个数最后一次出现的位置如果在\([L,R]\)之间,那么它一定不合法

我们保存父子关系中位置的最小值

每次对于\([L,R]\)这个区间,我们在第\(R\)颗线段树上查询

如果左子树的权值小于\(L\),说明左子树中一定有合法的答案,右子树同理

还有一个需要注意的:

因为每次查询的答案不会超过\(n\),所以我们可以不用离散化,对于大于\(n\)的数据不插入即可

为了方便处理,把所有数都加上了\(1\)避免权值为\(0\)的情况,查询时再减回来

可以对比一下这一题与\(\tt{array}\)

为什么这一题中的线段树要取\(min\),而那一颗中要取\(max\)

因为在那一题中,合法的位置是在大的一端,而这一题不同


代码

#include <iostream>
#include <cstdio>

using namespace std;

void fast_IO();

const int N = 2e5 + 10;

int n, m;
int rt[N];

struct CTree {
	
	int sz;
	int ls[N * 20], rs[N * 20], val[N * 20];
	
	void clear() { sz = 0; }
	
	int newnode() {
		++sz;
		ls[sz] = rs[sz] = val[sz] = 0;
		return sz;	
	}
	
	void mkchain(int &x, int y, int l, int r, int k, int v) {
		x = newnode();
		ls[x] = ls[y], rs[x] = rs[y];
		if (l == r)	return val[x] = v, void();
		int mid = l + r >> 1;
		if (k <= mid)
			mkchain(ls[x], ls[y], l, mid, k, v);
		else
			mkchain(rs[x], rs[y], mid + 1, r, k, v);
		val[x] = min(val[ls[x]], val[rs[x]]);
	}
	
	int query(int x, int l, int r, int k) {
		if (l == r)	return l - 1;
		int mid = l + r >> 1;
		if (val[ls[x]] < k)
			return query(ls[x], l, mid, k);
		else 
			return query(rs[x], mid + 1, r, k);
	}
	
} tr;

int main() {
	
	fast_IO();
	tr.clear();
	
	cin >> n >> m;
	
	int x, y;
	for (int i = 1; i <= n; ++i) {
		cin >> x;
		tr.mkchain(rt[i], rt[i - 1], 1, n + 1, x + 1, i);	
	}
	
	for (int i = 1; i <= m; ++i) {
		cin >> x >> y;
		cout << tr.query(rt[y], 1, n + 1, x) << endl;	
	}
	
	return 0;
}

void fast_IO() {
	ios :: sync_with_stdio(false);
	cin.tie(NULL), cout.tie(NULL);	
}
posted @ 2019-09-04 16:32  四季夏目天下第一  阅读(141)  评论(0编辑  收藏  举报