传送门:【Tsinsen】A1513. mex
题目分析:这题有两个解法,我想到的比较复杂,经过多次转化,学长后来和我说了一个比较简单的方法。
方法一:
我这题一开始是想用双向链表做的,每个l所在的位置保存这个l的所有的r,将这些r按照从小到大的顺序存到邻接表中。
然后由于线段长度1~n,所以最小值一定在0~n之间(想想为什么),所以我们为0~n的每个数保存合法区间(假如我们为0保存,那么就是保存0~第一个出现的0这一个区间,第一个0到第二个0这一个区间,……,最后一个0到n+1这一个区间),易知区间数最多为2n个。
然后我们用双向链表将有查询的点连起来。
现在假设0的一个合法区间为【L,R】(即【L,R】里面没有一个0),我们从表头开始找起,遇到一个坐标在【L,R】内的邻接表,将邻接表内右端点小于等于R的询问都更新答案,然后将他们删除(注意我们遍历邻接表时是从右端点小的遍历到大的,所以线性扫即可,然后最后把表头指针指到未删除的点就行了)。这样做的话,如果为一般的数据,还是可以接受的,但是遇到特殊数据如序列为0~n-1,查询全是【1,n】的,妥妥的退化到O(N^2)了,这是今早发现的问题,怪不得昨天一直TLE。
然后早上又想到了一种可行的解法:使用双向链表之前的方法不变,然后我们将双向链表替换成线段树。我们为线段树保存每个左端点上最小的右端点的大小(这个点不是一个查询的左端点时,设为INF(无穷大)),以及这个右端点对应的左端点的位置。然后我们同样是在0的一个合法区间【L,R】内查询,看是否存在一个最小右端点r满足r<=R,此时该r对应的左端点l就是需要更新的,然后我们将这个l所在邻接表内合法的r(r<=R我们视为合法)都更新答案掉(这些区间的答案就是0),然后我们将线段树内该下标l所在的位置的值更新为最小不合法的r(合法的都用完了,下一个就是最小不合法的了,再次注意邻接表内右端点是从小到大遍历的),然后相应的修改头指针为最小不合法的r。为了不进行特判,我们在邻接表的尾部插入一个INF,当这个l上的所有询问都更新完后,将线段树该位置设为INF,表示已经没有区间要更新了,因此在也不会到这个点了。
对于0的其他合法区间以及1~n的所有合法区间我们都如此来过,便可以将所有的询问都更新到了。
易知对于每个询问最多一次query以及一次update,每次复杂度logN,对于每个合法区间都有一次query,复杂度同为logN,所以最后的复杂度为O(NlogN)。
代码如下:
#include
#include
#include
#include
using namespace std ;
typedef long long LL ;
#define rep( i , a , b ) for ( int i = ( a ) ; i < ( b ) ; ++ i )
#define For( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define rev( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define clr( a , x ) memset ( a , x , sizeof a )
#define cpy( a , x ) memcpy ( a , x , sizeof a )
#define ls ( o << 1 )
#define rs ( o << 1 | 1 )
#define lson ls , l , m
#define rson rs , m + 1 , r
#define root 1 , 1 , n
#define mid ( ( l + r ) >> 1 )
const int MAXN = 200005 ;
const int INF = 0x3f3f3f3f ;
struct Node {
int v , idx ;
Node () {}
Node ( int v , int idx ) : v ( v ) , idx ( idx ) {}
bool operator < ( const Node& a ) const {
return v < a.v ;
}
} ;
struct Seg {
int l , r , idx ;
Seg () {}
Seg ( int l , int r , int idx ) : l ( l ) , r ( r ) , idx ( idx ) {}
bool operator < ( const Seg& a ) const {
return r > a.r ;
}
} ;
struct Edge {
int v , idx , n ;
Edge () {}
Edge ( int v , int idx , int n ) : v ( v ) , idx ( idx ) , n ( n ) {}
} ;
Seg seg[MAXN] ;
Edge E[MAXN] ;
int H[MAXN] , cntE ;
vector < int > p[MAXN] ;
int ans[MAXN] ;
int G[MAXN] ;
int minv[MAXN << 2] ;
int pos[MAXN << 2] ;
int n , m ;
void clear () {
cntE = 0 ;
clr ( H , -1 ) ;
For ( i , 0 , n ) p[i].clear () ;
clr ( G , INF ) ;
}
void addedge ( int u , int v , int idx ) {
E[cntE] = Edge ( v , idx , H[u] ) ;
H[u] = cntE ++ ;
}
void push_up ( int o ) {
if ( minv[ls] < minv[rs] ) {
minv[o] = minv[ls] ;
pos[o] = pos[ls] ;
} else {
minv[o] = minv[rs] ;
pos[o] = pos[rs] ;
}
}
void build ( int o , int l , int r ) {
if ( l == r ) {
minv[o] = G[l] ;
if ( minv[o] != INF ) pos[o] = l ;
else pos[o] = INF ;
return ;
}
int m = mid ;
build ( lson ) ;
build ( rson ) ;
push_up ( o ) ;
}
void update ( int x , int v , int o , int l , int r ) {
if ( l == r ) {
minv[o] = v ;
if ( minv[o] != INF ) pos[o] = l ;
else pos[o] = INF ;
return ;
}
int m = mid ;
if( x <= m ) update ( x , v , lson ) ;
else update ( x , v , rson ) ;
push_up ( o ) ;
}
int query ( int L , int R , int o , int l , int r ) {
if ( L <= l && r <= R ) return minv[o] <= R ? pos[o] : INF ;
int m = mid ;
if ( R <= m ) return query ( L , R , lson ) ;
if ( m < L ) return query ( L , R , rson ) ;
return min ( query ( L , R , lson ) , query ( L , R , rson ) ) ;
}
void scanf ( int& x , char c = 0 ) {
while ( ( c = getchar () ) < '0' ) ;
x = c - '0' ;
while ( ( c = getchar () ) >= '0' ) x = x * 10 + c - '0' ;
}
void solve () {
int v ;
clear () ;
For ( i , 0 , n ) p[i].push_back ( 0 ) ;
For ( i , 1 , n ) {
scanf ( v ) ;
if ( v > n ) continue ;
p[v].push_back ( i ) ;
}
For ( i , 0 , n ) p[i].push_back ( n + 1 ) ;
For ( i , 1 , m ) {
scanf ( seg[i].l ) ;
scanf ( seg[i].r ) ;
G[seg[i].l] = min ( G[seg[i].l] , seg[i].r ) ;
seg[i].idx = i ;
}
build ( root ) ;
sort ( seg + 1 , seg + m + 1 ) ;
For ( i , 1 , n ) if ( G[i] != INF ) addedge ( i , INF , 0 ) ;
For ( i , 1 , m ) addedge ( seg[i].l , seg[i].r , seg[i].idx ) ;
For ( i , 0 , n ) {
rep ( j , 1 , p[i].size () ) {
int l = p[i][j - 1] + 1 , r = p[i][j] - 1 ;
if ( r < l ) continue ;
while ( 1 ) {
int x = query ( l , r , root ) ;
if ( x == INF ) break ;
for ( int k = H[x] ; ~k ; k = E[k].n ) {
v = E[k].v ;
if ( v > r ) {
update ( x , v , root ) ;
break ;
}
H[x] = E[k].n ;
ans[E[k].idx] = i ;
}
}
}
}
For ( i , 1 , m ) printf ( "%d\n" , ans[i] ) ;
}
int main () {
while ( ~scanf ( "%d%d" , &n , &m ) ) solve () ;
return 0 ;
}
这个嘛比较简单。
我们建立一棵线段数,下标从0~n。
线段树的下标保存的是最后一个出现这个下标的原序列的位置。
假设我们有序列5 4 1 2 5
那么一开始有
0 0 0 0 0
表示所有数开始都位于0这个位置。
当我们从左往右扫时,依次变为
0 0 0 0 1
0 0 0 2 1
3 0 0 2 1
3 4 0 2 1
3 4 0 2 5
我们从左到右遍历序列。
当这个数大于n时忽略,否则将线段树中这个数对应的下标更新为这个数的位置。
更新完后,当这个位置R有询问【L,R】时,我们在线段树中查找线段树中保存的值小于L的最小下标,该下标就是这个询问的答案。
线段树就用一个最小值线段树即可。线段树中查找下标就用二分思想即可。
代码略。