@bzoj - 3339@ Rmq Problem
@description@
Input
Output
Sample Input
7 5
0 2 1 0 1 3 2
1 3
2 3
1 4
3 6
2 7
Sample Output
3
0
3
2
4
Hint
@solution@
在线不大好搞,因为 mex 函数不大支持合并,也没有比较好用的性质。
离线,如果采用每次加入一个数的方法也不大好搞。
但是反过来,假如我现在的区间的 mex 是 ans,删掉一个 a[x] 后如果剩下的区间没有与 a[x] 相同的数,可以得到新的 ans' = min(ans, a[x])。
这个证明还是比较直观的。也就是说我们可以写莫队了。我们接下来的算法就利用这个性质。
考虑固定左端点 l,维护以 l~n 中的下标作为右端点的 mex 值。记录 nxt[l] 表示 l 之后最近的与 a[l] 相同的位置。
每次从 l 转移到 l + 1 时就直接对区间 [l, nxt[l] - 1] 进行区间对 a[l] 取 min 的操作即可。
因为 mex 值是单调的(越远越大),这个很容易就线段树维护出来了。查询直接离线在线段树上查就可以了。
也许可以强制在线然后用可持久化线段树搞。
@accepted code@
#include<cstdio>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 200000;
vector<pair<int, int> >vec[MAXN + 5];
int n, m, a[MAXN + 5], ans[MAXN + 5];
int mex[MAXN + 5];
struct segtree{
struct node{
int l, r, mtag;
}t[4*MAXN + 5];
void build(int x, int l, int r) {
t[x].l = l, t[x].r = r, t[x].mtag = MAXN;
if( l == r ) return ;
int mid = (l + r) >> 1;
build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
}
void modify(int x, int l, int r, int k) {
if( l > t[x].r || r < t[x].l )
return ;
if( l <= t[x].l && t[x].r <= r ) {
t[x].mtag = min(t[x].mtag, k);
return ;
}
modify(x << 1, l, r, k), modify(x << 1 | 1, l, r, k);
}
int query(int x, int p) {
if( t[x].l == t[x].r ) return min(t[x].mtag, mex[p]);
int mid = (t[x].l + t[x].r) >> 1;
if( p <= mid ) return min(t[x].mtag, query(x << 1, p));
else return min(t[x].mtag, query(x << 1 | 1, p));
}
}T;
bool tag[MAXN + 5];
int nxt[MAXN + 5], adj[MAXN + 5];
void solve() {
int pos = 0;
for(int i=1;i<=n;i++) {
tag[a[i]] = true;
while( tag[pos] ) pos++;
mex[i] = pos;
}
for(int i=n;i>=1;i--) {
nxt[i] = (adj[a[i]] ? adj[a[i]] : n + 1);
adj[a[i]] = i;
}
T.build(1, 1, n);
for(int i=1;i<=n;i++) {
for(int j=0;j<vec[i].size();j++)
ans[vec[i][j].second] = T.query(1, vec[i][j].first);
T.modify(1, 1, nxt[i]-1, a[i]);
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
scanf("%d", &a[i]);
for(int i=1;i<=m;i++) {
int L, R; scanf("%d%d", &L, &R);
vec[L].push_back(make_pair(R, i));
}
solve();
for(int i=1;i<=m;i++)
printf("%d\n", ans[i]);
}
@details@
不得不说,通过删除一个数来维护新的区间信息的题目还是比较少见的。因为大多数题目都是维护加入一个数的情况。
人类智慧。