【Tsinsen】A1513. mex 线段树

传送门:【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的最小下标,该下标就是这个询问的答案。

线段树就用一个最小值线段树即可。线段树中查找下标就用二分思想即可。


代码略。

你可能感兴趣的:(线段树)