传送门:【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 <cstdio> #include <vector> #include <cstring> #include <algorithm> 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的最小下标,该下标就是这个询问的答案。
线段树就用一个最小值线段树即可。线段树中查找下标就用二分思想即可。
代码略。