hdu 4747 Mex【线段树】

题目

http://acm.hdu.edu.cn/showproblem.php?pid=4747

题意

给出一个序列,mex{}表示集合中没有出现的最小的自然数。然后 求sigma(mex (i , j)).

分析

在递推专题中看到这个题的,想了半天怎么感觉线段树可做啊,搜题解,果然线段树可做,递推做法有点神啊。

copy的题解:
考虑左端点固定时的所有区间的mex值,这个序列是一个非递减了。。。首先要明白。
初始就是求出mex[i]表示 mex(1 , i),对于每一个左端点,就是一个区间求和。
现在需要考虑的是左端点的改变对于序列的影响。。。
即左端点从i -> i + 1,mex[j]的改变。。。。即删去ai对于序列的影响。
如果 a[j] = a[i] 且 j > i ,不存在a[k] = a[i] j > k > i。即a[i]下一次出现的位置 。
根据mex的定义,我们知道 mex[k]不会改变, k >= j。因为删掉的ai还是存在于序列当中,所以不受影响。
之后需要考虑的是i +1 到 j - 1这段区间的mex值。。。删去了ai之后,使得原先mex值大于ai的,都会更新成ai。
很好理解。。。因为是没有出现的最小的,ai更小。。。
之前说过这是一个非递减的序列,所以原先mex值大于ai的也是一段连续的区间,所以我们可以找到最靠左的位置 r,使得mex[r] > a[i]。那么r 到 j - 1这段区间的mex值便 会更新为a[i]。
所以全部搞定。。。用线段树维护一下mex序列,区间更新,区间求和,然后一个查找就可以了。

代码

#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;

#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
#define LL long long
const int maxn = 200005;
int add[maxn<<2];
int Max[maxn<<2];
int mex[maxn<<2];
LL sum[maxn<<2];
void PushUp(int rt) {
    sum[rt] = sum[rt<<1] + sum[rt<<1|1];
    Max[rt] = max(Max[rt<<1] , Max[rt<<1|1]);
}
void PushDown(int rt,int m) {
    if (add[rt]!=-1) {
        add[rt<<1] = add[rt<<1|1] = add[rt];
        Max[rt<<1] = Max[rt<<1|1] = add[rt];
        sum[rt<<1] = add[rt] * (m - (m >> 1));
        sum[rt<<1|1] = add[rt] * (m >> 1);
        add[rt] = -1;
    }
}
void build(int l,int r,int rt) {
    add[rt] = -1;
    if (l == r) {
        Max[rt]=sum[rt]=mex[l];
        return ;
    }
    int m = (l + r) >> 1;
    build(lson);
    build(rson);
    PushUp(rt);
}
void update(int L,int R,int c,int l,int r,int rt) {
    if (L <= l && r <= R) {
        add[rt] = c;
        Max[rt] = c;
        sum[rt] = (LL)c * (r - l + 1);
        return ;
    }
    PushDown(rt , r - l + 1);
    int m = (l + r) >> 1;
    if (L <= m) update(L , R , c , lson);
    if (m < R) update(L , R , c , rson);
    PushUp(rt);
}
LL query(int L,int R,int l,int r,int rt) {
    if (L <= l && r <= R) {
        return sum[rt];
    }
    PushDown(rt , r - l + 1);
    int m = (l + r) >> 1;
    LL ret = 0;
    if (L <= m) ret += query(L , R , lson);
    if (m < R) ret += query(L , R , rson);
    return ret;
}
int getpos(int v,int l,int r,int rt)
{
    if(l==r)return l;
    PushDown(rt,r-l+1);
    int m=(l+r)>>1;
    if(Max[rt<<1] > v)return getpos(v, lson);
    return getpos(v, rson);
}

int nex[maxn],a[maxn];
map<int,int>pre;
int main() {
    int N;
    while(~scanf("%d",&N)&&N){
        pre.clear();
        for(int i=1;i<=N;i++)nex[i]=N;
        int Min=0;
        for(int i=1;i<=N;i++){
            scanf("%d",&a[i]);
            int t=a[i];
            if(pre.count(t))nex[pre[t]]=i-1;
            pre[t]=i;
            while(pre.count(Min))Min++;
            mex[i]=Min;
        }
        build(1 , N , 1);
        LL res=0;
        for(int i=1;i<=N;i++){
            res+=query(i,N,1,N,1);
            if(Max[1]<a[i])continue;
            int pos=getpos(a[i],1,N,1);
            update(pos,nex[i],a[i],1,N,1);
        }
        printf("%lld\n", res);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值