3.自然数(mex.cpp)
【问题描述】
有一年,有道题目叫mex,Fanvree三秒钟就切了,所以今天,他要把题目改良,出到NOIP上。
我们定义mex(i,j)为序列中第i项到第j项所没有出现的最小自然数。
Fanvree的题目是,给你一个序列,求Σ1<=i<=j<=n mex(i,j)
【输入格式】
第一行一个整数n,表示序列大小。
接下来一行,n个整数,描述序列
【输出格式】
只含一个整数,表示Σ1<=i<=j<=n mex(i,j)
【输入样例】
3
0 1 3
【输出样例】
5
【输入输出样例说明】
mex(1,1)=1,
mex(1,2)=2,
mex(1,3)=2,
mex(2,2)=0,
mex(2,3)=0,
mex(3,3)=0,
1+2+2+0+0+0=5。
【数据规模与约定】
对于20%的数据,满足n<=200
对于50%的数据,满足n<=3000
【问题描述】
有一年,有道题目叫mex,Fanvree三秒钟就切了,所以今天,他要把题目改良,出到NOIP上。
我们定义mex(i,j)为序列中第i项到第j项所没有出现的最小自然数。
Fanvree的题目是,给你一个序列,求Σ1<=i<=j<=n mex(i,j)
【输入格式】
第一行一个整数n,表示序列大小。
接下来一行,n个整数,描述序列
【输出格式】
只含一个整数,表示Σ1<=i<=j<=n mex(i,j)
【输入样例】
3
0 1 3
【输出样例】
5
【输入输出样例说明】
mex(1,1)=1,
mex(1,2)=2,
mex(1,3)=2,
mex(2,2)=0,
mex(2,3)=0,
mex(3,3)=0,
1+2+2+0+0+0=5。
【数据规模与约定】
对于20%的数据,满足n<=200
对于50%的数据,满足n<=3000
对于100%的数据,满足n<=200000,0<=ai<=109
题解:不想写代码了,把大象的代码贴上来,虽然写的丑了点,加了注释还是勉强可以的mex(1,i).
可以知道mex(i,i),mex(i,i+1)到mex(i,n)是递增的。
然后使用线段树维护,需要不断删除前面的数。
比如删掉第一个数a[1]. 那么在下一个a[1]出现前的大于a[1]
的mex 值都要变成a[1]
因为mex 是单调递增的,所以找到第一个mex>a[1]的位置,到
下一个a[1]出现位置,这个区间的值变成a[1].
然后需要线段树实现区间修改和区间求和。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 200005
using namespace std;
struct node{
int left,right;
long long sum,sign,mx;
}tree[N<<2];
int n;
int a[N],sg[N];
bool vis[N];
int nxt[N],p[N];
void built(int id,int l,int r)
{
tree[id].left=l;tree[id].right=r;tree[id].sign=-1;
if(l==r){ tree[id].sum=tree[id].mx=(long long)sg[l];return ; }
int mid=(l+r)>>1;
built(id<<1,l,mid);built(id<<1|1,mid+1,r);
tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
tree[id].mx=max(tree[id<<1].mx,tree[id<<1|1].mx);
}
void downdate(int id)
{
tree[id<<1].sign=tree[id<<1|1].sign=tree[id].sign;
tree[id<<1].mx=tree[id<<1|1].mx=tree[id].sign;
tree[id<<1].sum=(tree[id<<1].right-tree[id<<1].left+1)*tree[id].sign;
tree[id<<1|1].sum=(tree[id<<1|1].right-tree[id<<1|1].left+1)*tree[id].sign;
tree[id].sign=-1;
}
void update(int id,int l,int r,int w)
{
if(tree[id].left==l && tree[id].right==r)
{
tree[id].sign=w;tree[id].mx=(long long)w;
tree[id].sum=(long long)(tree[id].right-tree[id].left+1)*w;
return ;
}
if(tree[id].sign!=-1) downdate(id);
int mid=(tree[id].left+tree[id].right)>>1;
if(r<=mid) update(id<<1,l,r,w);
else if(l>mid) update(id<<1|1,l,r,w);
else update(id<<1,l,mid,w),update(id<<1|1,mid+1,r,w);
tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
tree[id].mx=max(tree[id<<1].mx,tree[id<<1|1].mx);
}
int query(int id,int x)
{
if(tree[id].left==tree[id].right) return tree[id].left;
if(tree[id].sign!=-1) downdate(id);
int mid=(tree[id].left+tree[id].right)>>1;
if(tree[id<<1].mx>x) return query(id<<1,x);
else return query(id<<1|1,x);
}
int main()
{
// freopen("mex.in","r",stdin);
// freopen("mex.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]>n) a[i]=n+1;
}
int k=0;
for(int i=1;i<=n;i++)//sg表示1到i区间的mex
{
vis[a[i]]=true;
while(vis[k]) k++;
sg[i]=k;
}
built(1,1,n);long long ans=0;
for(int i=1;i<=n;i++) p[a[i]]=n+1;
for(int i=n;i>=1;i--) nxt[i]=p[a[i]],p[a[i]]=i;//寻找第i为以后第一个a【i】出现的地方
for(int i=1;i<=n;i++)
{
ans+=tree[1].sum;
if(tree[1].mx>a[i])
{
int l=query(1,a[i]);
int r=nxt[i];
if(l<=r-1) update(1,l,r-1,a[i]);
}
update(1,i,i,0);//把第i个点变为0,即对答案没有影响。
}
printf("%lld\n",ans);
return 0;
}