题目链接: P4137 Rmq Problem / mex
大致题意
给定一个长度为 n n n的序列, 有 m m m次询问操作.
每次询问 [ l , r ] [l, r] [l,r]区间的 m e x mex mex是多少.
解题思路
解法一: 权值线段树 + 询问离线
我们考虑把所有的询问按照右端点 r r r从小到大排序, 用权值线段树维护 [ 1 , r ] [1, r] [1,r]区间中所有数值最后一次出现位置的最小值.
对于询问区间 [ l , r ] [l, r] [l,r], 我们在树上进行二分, 如果当前值域左区间的最小出现位置要小于 l l l, 则访问左区间. 反之访问右区间.
对于没有出现过的值, 树中会认为出现在了 0 0 0位置, 是不会影响答案正确性的.
解法二: 可持久化权值线段树
同解法一, 但可以通过可持久化的方式, 转化为在线做法.
解法三: 莫队 + 值域分块
我们同样可以考虑用莫队去维护.
考虑到维护 [ l , r ] [l, r] [l,r]信息时, 由于我们需要求 m e x mex mex, 我们区间扩增和收缩时, 都不能 O ( 1 ) O(1) O(1)维护出当前区间的答案.
于是我们考虑对值域进行分块, 块内维护当前值域数值出现过的个数(去重后).
这样我们可以在 O ( n ) O(\sqrt{n}) O(n)的时间内查询出区间答案. 在 O ( n ) O(\sqrt{n}) O(n)的时间内维护区间信息.
AC代码
权值线段树
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E5 + 10, L = 0, R = N - 5;
int w[N];
struct node {
int l, r;
int val;
}t[N << 2];
void pushup(int x) {
t[x].val = min(t[x << 1].val, t[x << 1 | 1].val); }
void build(int l, int r, int x = 1) {
t[x] = {
l, r, 0 };
if (l == r) return;
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}
void modify(int a, int c, int x = 1) {
if (t[x].l == t[x].r) {
t[x].val = c;
return;
}
int mid = t[x].l + t[x].r >> 1;
modify(a, c, x << 1 | (a > mid));
pushup(x);
}
int ask(int pos, int x = 1) {
if (t[x].l == t[x].r) return t[x].l;
if (t[x << 1].val < pos) return ask(pos, x << 1);
return ask(pos, x << 1 | 1);
}
struct query {
int l, r, id;
bool operator< (const query& t) const {
return r < t.r; }
}; vector<query> area;
int res[N];
int main()
{
int n, m; cin >> n >> m;
build(L, R);
rep(i, n) scanf("%d", &w[i]);
rep(i, m) {
int l, r; scanf("%d %d", &l, &r);
area.push_back({
l, r, i });
}
sort(area.begin(), area.end());
int pos = 0;
for (auto& [l, r, id] : area) {
while (pos + 1 <= r) {
++pos;
modify(w[pos], pos);
}
res[id] = ask(l);
}
rep(i, m) printf("%d\n", res[i]);
return 0;
}
可持久化线段树
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E5 + 10, L = 0, R = N - 5;
struct node {
int l, r;
int val;
}t[N * 20];
int root[N], ind;
int build(int a, int c, int tl, int tr, int p) {
int x = ++ind; t[x] = t[p];
if (tl == tr) {
t[x].val = c;
return x;
}
int mid = tl + tr >> 1;
if (a <= mid) t[x].l = build(a, c, tl, mid, t[p].l);
else t[x].r = build(a, c, mid + 1, tr, t[p].r);
t[x].val = min(t[t[x].l].val, t[t[x].r].val);
return x;
}
int ask(int tl, int tr, int pos, int x) {
if (tl == tr) return tl;
int mid = tl + tr >> 1;
if (t[t[x].l].val < pos) return ask(tl, mid, pos, t[x].l);
return ask(mid + 1, tr, pos, t[x].r);
}
int main()
{
int n, m; cin >> n >> m;
rep(i, n) {
int x; scanf("%d", &x);
root[i] = build(x, i, L, R, root[i - 1]);
}
rep(i, m) {
int l, r; scanf("%d %d", &l, &r);
printf("%d\n", ask(L, R, l, root[r]));
}
return 0;
}
莫队
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E5 + 10, B = 450;
int w[N];
struct mo {
int l, r, id;
bool operator< (const mo& t) const {
if (l / B != t.l / B) return l < t.l;
return l / B & 1 ? r < t.r : r > t.r;
}
}; vector<mo> area;
int res[N];
int cou[N], bcou[B];
void add(int c) {
if (++cou[c] == 1) bcou[c / B]++; }
void sub(int c) {
if (--cou[c] == 0) bcou[c / B]--; }
int ask() {
for (int i = 0; i < B; ++i) {
if (bcou[i] == B) continue;
int l = B * i, r = l + B - 1;
for (int j = l; j <= r; ++j) {
if (!cou[j]) return j;
}
}
assert(0);
}
int main()
{
int n, m; cin >> n >> m;
rep(i, n) scanf("%d", &w[i]);
rep(i, m) {
int l, r; scanf("%d %d", &l, &r);
area.push_back({
l, r, i });
}
sort(area.begin(), area.end());
int L = 1, R = 0;
for (auto& [l, r, id] : area) {
while (l < L) add(w[--L]);
while (r > R) add(w[++R]);
while (L < l) sub(w[L++]);
while (R > r) sub(w[R--]);
res[id] = ask();
}
rep(i, m) printf("%d\n", res[i]);
return 0;
}