BZOJ 3339: Rmq Problem 穿了棉袄的线段树

Time Limit: 20 Sec Memory Limit: 128 MB
Submit: 1332 Solved: 709

Description

这里写图片描述

Input

这里写图片描述

Output

这里写图片描述

Sample Input

7 5

0 2 1 0 1 3 2

1 3

2 3

1 4

3 6

2 7

Sample Output

3

0

3

2

4

HINT

这里写图片描述

Source

By Xhr


题解:

这道题的线段树显然不是裸的


我们就是求一个区间mex嘛,于是我们可以把所有询问离线一下,按照左端点排序,然后每次维护的是对于这个左端点的一直到n的线段树,每个点都表示这个点到当前的这个左端点的mex值,计算方法其实很简单,我们记录一个nxt数组,nxt数组表示和这个点的值相同的值的下一个位置,如果这个值为0的话就赋为n+1,现在如果我们要从l转移到l+1,那么我们就看这个l,如果这个点可以影响到后面的话,也就是说,如果比后面的某个点的mex值小的话,那么删除了之后,显然这个点的mex值就应该变成l的值,因为l的值被删了,而mex表示的是第一个没有出现的值如果本来后面的就小的话就不影响,所以我们就可以讨论一下大小关系,然后取min值,我是抄的黄学长的代码,感觉线段树写的好不清真啊,主要是因为头有点昏,所以直接敲了一遍a了,希望复习吧

第一遍可以O(n)求mex,因为我们发现最开始从1到x的mex值都是递增的,因此可以利用这个递增性,O(n)地去求最开始的mex值,nxt值也可以O(n)求,只需要倒着for就可以了,真的是好巧妙啊,这题总之处处都很巧妙,很有意思


#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int INF = 0x7fffffff;
int n,m,k=0;
const int MAXN = 200005;
int a[MAXN],sg[MAXN],ans[MAXN],nxt[MAXN],last[MAXN];
struct Tree{ int mex; } tree[ MAXN * 4 ];
bool mark[200001];
struct Q{ int l, r, id; } q[200005];
bool cmp(Q a,Q b) { return a.l < b.l; }
void pushdown( int l, int r, int rt ) {
    if( l == r ) return;
    tree[ rt << 1 ].mex = min( tree[rt].mex, tree[ rt << 1 ].mex );
    tree[ rt << 1 | 1 ].mex = min( tree[rt].mex, tree[ rt << 1 | 1 ].mex );
}
int query( int R, int l, int r, int rt ) {
    if( tree[rt].mex != INF ) pushdown( l, r, rt );
    if( l == r ) return tree[rt].mex;
    int mid = ( l + r ) >> 1;
    if( R <= mid ) return query( R, l, mid, rt << 1 );
    else           return query( R, mid + 1, r, rt << 1 | 1 );
}
void modify( int L, int R, int val, int l, int r, int rt ) {
    if( tree[rt].mex != INF ) pushdown( l, r, rt );
    if( L == l && R == r ) { tree[rt].mex = min( tree[rt].mex, val ); return ; }
    int mid = ( l + r ) >> 1;
    if( R <= mid )     modify( L, R, val, l, mid, rt << 1 );
    else if( L > mid ) modify( L, R, val, mid + 1, r, rt << 1 | 1 );
    else { modify( L, mid, val, l, mid, rt << 1 ); modify( mid + 1, R, val, mid + 1, r, rt << 1 | 1 ); }
}
void build( int l, int r, int rt ) {
    tree[rt].mex = INF;
    if( l == r ) { tree[rt].mex = sg[l]; return; }
    int mid = ( l + r ) >> 1;
    build( l, mid, rt << 1 );
    build( mid + 1, r, rt << 1 | 1 );
}
int main( ) {
    scanf( "%d%d", &n, &m );
    for( register int i = 1; i <= n; i++ ) scanf( "%d", &a[i] );
    for( register int i = 1; i <= n; i++ ) {
        mark[a[i]] = 1;
        if( a[i] == k ) while( mark[k] ) k++;
        sg[i] = k; 
    }
    build( 1, n, 1 );
    for( register int i = n; i > 0; i-- ) nxt[i] = last[a[i]], last[a[i]] = i;
    for( register int i = 1; i <= m; i++ ) { scanf( "%d%d", &q[i].l, &q[i].r ); q[i].id = i; }
    sort( q + 1, q + m + 1, cmp );
    int now = 1;
    for( register int i = 1; i <= m; i++ ) {
        while( now < q[i].l ) {
            if( !nxt[now] ) nxt[now] = n + 1;
            modify( now, nxt[now] - 1, a[now], 1, n, 1 );
            now++;
        }
        ans[q[i].id] = query( q[i].r, 1, n, 1 );
    }
    for( register int i = 1; i <= m; i++ ) printf( "%d\n", ans[i] );
    return 0;
}

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值