求区间mex——离线线段树做法

区间mex

最近做牛客上的题(little w and Discretization)的时候,遇到了这样一种需求,给定一个序列,然后再给出一系列查询,求序列中,\(l\)\(r\)之间的数的mex(简单介绍:mex(S) 的值为集合 S 中没有出现过的最小自然数)。

花了点时间做出这题之后,稍微了解了一些关于求区间mex的线段树离线写法,姑且做一个笔记


模板问题

P4137 Rmq Problem / mex - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

核心思想

用线段树维护一个序列,序列中储存该下标在给出的序列中最后一次出现的位置,如t[x] = pos,意味\(x\)在原序列中最后一次出现的下标为\(pos\)

线段树维护一个区间最小值,线段树的点的含义是该数最后一次出现的下标,线段树区间的含义是这一段中最小的最后一次出现的下标。

查询前,我们读入所有的查询的\(l,r\),将它们按右端点排序。从左到右遍历整个给出的区间,更新每一个树的最后出现的位置(使用线段树的单点修改)。当位置达到当前区间的右边界时,在线段树上进行二分(这就是为什么线段树要维护区间最小的原因),如果位置小于左端点,说明在所询问的段中这个数没有出现过,是可行的值。最后找到mex。

更多细节可以配合代码理解

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 1000;
int n, m;
struct Segtree {
	int t[(maxn << 2) + 10];
	void init() {
		for (int i = 1; i <= maxn; i++)
			t[i] = -1;
	}
	void update(int val, int pos, int l = 1, int r = n + 1, int rt = 1) {
		if (l == r) {
			t[rt] = pos;
			return;
		}
		int mid = (l + r) / 2;
		if (val <= mid)
			update(val, pos, l, mid, rt << 1);
		else if (val > mid)
			update(val, pos, mid + 1, r, rt << 1 | 1);
		t[rt] = min(t[rt << 1], t[rt << 1 | 1]);
	}
	int query(int pos, int l = 1, int r = n + 1, int rt = 1) {//l,r是二分的左右端点
		if (l == r)
			return l;
		int mid = (l + r) / 2;
		if (t[rt << 1] < pos)					//如果这一段中有一个数的下标最后一次出现在左区间左端说明该值可行,往更小的数继续找
			return query(pos, l, mid, rt << 1);
		else
			return query(pos, mid + 1, r, rt << 1 | 1);
	}
};
int a[maxn + 10];
struct Ask {
	int l, r;
	int id;
	int mex;
};

Segtree t1;
void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		a[i]++;				//因为线段树要处理0会很麻烦,不如都从1开始记,最后再减去1
	}
	t1.init();
	vector<Ask> v(m + 10);
	for (int i = 1; i <= m; i++) {
		cin >> v[i].l >> v[i].r;
		v[i].id = i;
	}

	sort(v.begin() + 1, v.begin() + 1 + m, [](Ask a, Ask b) {return a.r < b.r; });//按右端点排序

	for (int i = 1, p = 1; i <= n; i++) {
		if (a[i] <= n) {
			t1.update(a[i], i);				//更新每一个数最后出现的位置
		}
		while (p <= m && v[p].r == i) {
			v[p].mex = t1.query(v[p].l);
			p++;
		}
	}

	sort(v.begin() + 1, v.begin() + 1 + m, [](Ask a, Ask b) {return a.id < b.id; });//还原顺序
	for (int i = 1; i <= m; i++) {
		cout << v[i].mex - 1 << "\n";
	}
	return;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	solve();
	return 0;
}
posted @ 2021-11-02 19:58  icey_z  阅读(1128)  评论(0)    收藏  举报