前置指使:可持久化线段树
题解: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;
}
小结:
对于类似的题套路性很强,但是转化具有一定的思维难度。要多做题,多见题才更能掌握主席树的应用。
标签:rt,Rmq,NOIP,int,题解,read,le,值域,mex From: https://www.cnblogs.com/Ydoc770/p/18547716