【NOIP2016提高A组8.11】自然数

本文介绍了一种使用线段树解决区间修改问题的方法,通过实例详细解析了如何求解给定序列的mex值,并利用线段树进行高效更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

这里写图片描述

Input

第一行n
第二行n个数

Output

答案

Sample Input

3
0 1 3

Sample Output

5

Data Constraint

n<=200000
ai<=109

Solution

50分暴力明显,用标记数组随便YY
但是你可能在想要离散化,其实凡是大于n的都直接等于n就行了,mex最大就是n
想:如果知道[1,n]的mex,能不能求到[2,n]
显然mex[1,i]是单调递增的,当第一个数被删掉时,如果后面原来比这个数大,并且在下一个数出现之前,那就变成这个数
区间修改就可以用线段树了
加上一个线段树上二分
线段树初始化为mex[1,1],mex[1,2],mex[1,3]……一次修改变成mex[2,2],mex[2,3]……
修改n-1次,每次求和就行了

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 201000
#define ll long long
using namespace std;
int n,a[N],tot=0,bz[N],next[N];
ll ans=0;
struct note{
    ll m,h,l;
};
note t[N*25];
void down(int v,int i,int j)
{
    if(t[v].l==-1) return;
    int m=(i+j)/2;
    t[v*2].m=t[v*2+1].m=t[v*2+1].l=t[v*2].l=t[v].l;
    t[v*2].h=t[v].l*(m-i+1);t[v*2+1].h=t[v].l*(j-m);t[v].l=-1;
}
void insert(int v,int i,int j,int x,int y,ll z)
{
    if(i==x&&j==y){t[v].l=t[v].m=z;t[v].h=z*(y-x+1);return;}
    int m=(i+j)/2;down(v,i,j);
    if(y<=m) insert(v*2,i,m,x,y,z);
    else if(x>m) insert(v*2+1,m+1,j,x,y,z);
         else insert(v*2,i,m,x,m,z),insert(v*2+1,m+1,j,m+1,y,z);
    t[v].m=max(t[v*2].m,t[v*2+1].m);t[v].h=t[v*2].h+t[v*2+1].h;
}
int find(int v,int i,int j,ll x)
{
    if(i==j){return i;}
    int m=(i+j)/2;down(v,i,j);
    if(t[v*2].m>x) return find(v*2,i,m,x);
    else return find(v*2+1,m+1,j,x);
}
int main()
{
    scanf("%d",&n);
    fo(i,1,n) scanf("%d",&a[i]);
    //-----------------------------
    memset(bz,0,sizeof(bz));
    int k=0;
    fo(j,1,n)
    {
        int i=a[j]>n?n:a[j];
        if(bz[i]==0) bz[i]=j;
        else next[bz[i]]=j,bz[i]=j;
        while (bz[k]>0) k++;
        ans+=(ll)k;insert(1,1,n,j,j,k);
    }
    fo(i,1,n) next[i]=(next[i]==0)?n+1:next[i];
    fo(i,1,n-1){
        int k=find(1,1,n,a[i]);
        if(t[1].m<a[i]) k=n+1;
        if (next[i]-1>=k) insert(1,1,n,k,next[i]-1,a[i]);
        insert(1,1,n,i,i,0);
        ans+=t[1].h;
    }
    printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值