一种不太难想的做法?但可能码量大一些???
先把所有的询问按照 (l) 排序。
现在考虑处理出一颗线段树维护一个区间,其中某一个叶子节点表示的含义,为:当前 (l) 到 ((l quad-quad n ))这一区间内未出现的自然数是谁。
那么最初是的 (l) 是 (1),即每个叶子节点都表示从 (1) 号点到 (x) 点这一段区间内未出现的最小自然数。
那么我们要先处理出每个位置到 (1) 号位置这一区间的最小未出现的自然数。
这个处理可以通过维护最小出现次数的权值线段树来做。
然后将处理出来的结果存入一个线段树内。
考虑对于 $l = 1 $ 的点,其答案直接通过这个线段树来作查询即可。
因为按照 (l) 排序了,所以接下来的我们需要去更新线段树的其他节点。
然后考虑让 (l) 向左移动,我们可以发现,删去一个数 (a[l]) 其实就是让后面的点对其取 (min)。所以可以维护一个取 (min) 标记。
但是考虑到某个元素可能重复出现,所以可以再开一个 (vector) 维护每个点出现的位置,然后取 (min) 操作就是对此点从当前位置到其下一个出现的位置这一段区间对 (x) 取 (min)
具体实现上因为在 (vector) 上二分找位置不太现实,然而我们已经按照 (l)排序,每个 (vector) 内存的位置单调递增,所以可以维护一个 (fir[x]) 表示当前的这个权值所对应的 (vector) 到了那个点,每次更新完后(++ fir)
代码:
#include<bits/stdc++.h>
using namespace std;
int read() {
char cc = getchar(); int cn = 0, flus = 1;
while(cc < '0' || cc > '9') { if( cc == '-' ) flus = -flus; cc = getchar(); }
while(cc >= '0' && cc <= '9') cn = cn * 10 + cc - '0', cc = getchar();
return cn * flus;
}
const int N = 2e5 + 5;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define ls(x) ( x * 2 )
#define rs(x) ( x * 2 + 1 )
#define Size
#define inf 123456789
struct Tree { int l, r, mi, mark; }tr[N * 4], t[N * 4];
struct Q { int l, r, id, ans; }q[N];
vector< int> vec[N];
int a[N], n, m, fir[N], cnt;
bool cmp( Q x, Q y ) {
return ( x.l == y.l ) ? x.r < y.r : x.l < y.l ;
}
bool cmp2( Q x, Q y ) {
return x.id < y.id;
}
// 权值线段树
void insert1( int x, int l, int r, int wh ) {
if( l == r ) { ++ tr[x].mi; return ; };
int mid = ( l + r ) >> 1;
if( mid >= wh ) insert1( ls(x), l, mid, wh );
else insert1( rs(x), mid + 1, r, wh );
tr[x].mi = min( tr[ls(x)].mi, tr[rs(x)].mi ); //用权值线段树维护区间最小出现次数
}
int Query1( int x, int l, int r ) {
if( l == r ) return l;
int mid = ( l + r ) >> 1;
if( !tr[ls(x)].mi ) return Query1( ls(x), l, mid ); // 如果左儿子内最小出现次数为0,往左儿子走
else return Query1( rs(x), mid + 1, r ); // 否则往右儿子走
}
// 线段树
void pushmark( int x ) { //区间取min的下传
if( t[x].mark != inf ) {
t[ls(x)].mark = min( t[ls(x)].mark, t[x].mark );
t[ls(x)].mi = min( t[ls(x)].mi, t[ls(x)].mark );
t[rs(x)].mark = min( t[rs(x)].mark, t[x].mark );
t[rs(x)].mi = min( t[rs(x)].mi, t[rs(x)].mark );
}
}
void Ins( int x, int l, int r, int wh, int val ) {
t[x].mark = inf ; //注意mark初始化为inf
if( l == r ) { t[x].mi = val; return ;}
int mid = ( l + r ) >> 1 ;
if( mid >= wh ) Ins( ls(x), l, mid, wh, val );
else Ins( rs(x), mid + 1, r, wh, val );
t[x].mi = min( t[ls(x)].mi, t[rs(x)].mi ); // 线段树同样维护区间最小值,方便取min操作
}
void Cover( int x, int l, int r, int ll, int rr, int val ) {
if( ll <= l && r <= rr ) {
t[x].mark = min( t[x].mark, val ) ; //取min
t[x].mi = min( t[x].mi, t[x].mark ) ;
return ;
}
if( l > rr || r < ll ) return ; //区间越界
pushmark(x) ; int mid = ( l + r ) >> 1 ;
Cover( ls(x), l, mid, ll, rr, val ) ;
Cover( rs(x), mid + 1, r, ll, rr, val ) ;
return ;
}
int Query2( int x, int l, int r, int wh ) { // 询问位置为wh的点的答案
if( l == r ) return t[x].mi;
pushmark(x); int mid = ( l + r ) >> 1;
if( mid >= wh ) return Query2( ls(x), l, mid, wh );
else return Query2( rs(x), mid + 1, r, wh );
}
void input() {
n = read(), m = read();
rep( i, 1, n ) {
a[i] = read() + 1; //加1,方便线段树处理
if( a[i] > n ) continue; // 如果a[i] 大于 n 就没有必要加入
vec[a[i]].push_back(i);
}
rep( i, 1, n ) vec[i].push_back(n + 1); //权值至多有n种,在每个点的vector最后面插入一个元素(n + 1)。
}
void solve() {
int x, ll = 1;
rep( i, 1, n ) {
if( a[i] <= n ) insert1( 1, 1, n, a[i] ); //如果比n小才插入权值线段树
x = Query1( 1, 1, n ), Ins( 1, 1, n, i, x );
// x为询问 1-n 中未出现的权值,然后将作为位置i的答案插入线段树。
}
rep( i, 1, m ) q[i].l = read(), q[i].r = read(), q[i].id = i;
sort( q + 1, q + m + 1, cmp );
rep( i, 1, m ) {
while( ll < q[i].l ) {
x = a[ll];
if( x <= n ) Cover( 1, 1, n, ll + 1, vec[x][fir[x] + 1] - 1, x ), ++ fir[x];
++ ll;
}
q[i].ans = Query2( 1, 1, n, q[i].r ) - 1;
}
}
signed main()
{
input() , solve() ;
sort( q + 1, q + m + 1, cmp2 );
rep( i, 1, m ) printf("%d
", q[i].ans );
return 0;
}