前置指使:可持久化线段树
题解: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;
}
小结:
对于类似的题套路性很强,但是转化具有一定的思维难度。要多做题,多见题才更能掌握主席树的应用。