题解 P4137 【Rmq Problem / mex】

一种不太难想的做法?但可能码量大一些???

先把所有的询问按照 \(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\n", q[i].ans );
	return 0;
}
posted @ 2019-04-11 08:44  Soulist  阅读(156)  评论(0编辑  收藏  举报