NOIP集训 P4137 Rmq Problem / mex 题解

前置指使:可持久化线段树

题解:P4137 Rmq Problem / mex

有一个长度为 \(n\) 的数组 \(\{ a_1,a_2,...,a_n \}\)
\(m\) 次询问,每次询问一个区间内最小没有出现过的自然数。

Input

第一行,两个正整数 \(n,m\)
第二行,\(n\) 个非负整数 \(a_1, a_2,...,a_n\)
接下来 \(m\) 行,每行两个正整数 \(l,r\),表示一次询问。

Output

输出 \(m\) 行,每行一个数,依次表示每个询问的答案。

Note

对于 \(100\%\) 的数据:\(1\le n,m\le 2e5, 1\le l\le r\le n,0\le a_i\le 2e5\)

分析

思考 \(mex\) 的本质。对于一段给定的区间,\(mex\) 即是从 \(0\) 开始存在的连续值域的最大值加 \(1\)。若 \(0\) 不在区间值域中则 \(mex\)\(0\)
在值域线段树上考虑问题。序列中的值暂时不用考虑,因为可以二分值域直接处理。对于 \([l,r]\)\(mex\) ,即是要找到一个最小的 \(i\) ,使得这个 \(i\) 最后出现的位置 \(last_i\) 小于 \(l\)
所以可以在主席树上维护每个值出现位置的 \(min\),注意加入时要把 \(x\) 加上 \(1\) ,再在查询时减去 \(1\) 防止值域出现问题.
数据不需要离散化。同时,对于大于 \(n\)\(a_i\) 显然对答案没有任何贡献,因为这时必然存在一个小于等于 \(n\)\(mex\)

AC代码:

#include<bits/stdc++.h>
using namespace std;

inline int read() {
	int f = 1, otto = 0;
	char a = getchar();
	while(!isdigit(a)) {
		if(a == '-') f = -1;
		a = getchar();
	}
	while(isdigit(a)) {
		otto = (otto << 1) + (otto << 3) + (a ^ 48);
		a = getchar();
	}
	return f * otto;
}

const int maxn = 2e5 + 10;
int ver;
int rt[maxn], tr[maxn << 5], ls[maxn << 5], rs[maxn << 5];

void upd(int &u1, int u2, int l, int r, int num, int loc) {
	if(!u1) u1 = ++ver;
	if(l == r) return tr[u1] = loc, void(0);
	
	int mid = l + r >> 1;
	if(num <= mid) rs[u1] = rs[u2], upd(ls[u1], ls[u2], l, mid, num, loc);
	else ls[u1] = ls[u2], upd(rs[u1], rs[u2], mid + 1, r, num, loc);
	
	return tr[u1] = min(tr[ls[u1]], tr[rs[u1]]), void(0);
} 

int ask(int u, int l, int r, int lim) {
	if(l == r) return l;
	
	int mid = l + r >> 1;
	if(tr[ls[u]] < lim) return ask(ls[u], l, mid, lim);
	else return ask(rs[u], mid + 1, r, lim); 
}

int main() {
	int n = read() + 1, m = read();
	for(int i = 1; i < n; i++) {
		int x = read() + 1;
		if(x > n) rt[i] = rt[i - 1];
		else upd(rt[i], rt[i - 1], 1, n, x, i);
	}
	while(m--) {
		int l = read(), r = read();
		printf("%d\n", ask(rt[r], 1, n, l) - 1);
	}
	return 0;
}

小结:

对于类似的题套路性很强,但是转化具有一定的思维难度。要多做题,多见题才更能掌握主席树的应用。

posted @ 2024-11-16 17:06  Ydoc770  阅读(4)  评论(0编辑  收藏  举报