Problem Description
Mex is a function on a set of integers, which is universally used for impartial game theorem. For a non-negative integer set S, mex(S) is defined as the least non-negative integer which is not appeared in S. Now our problem is about mex function on a sequence.
Consider a sequence of non-negative integers {ai}, we define mex(L,R) as the least non-negative integer which is not appeared in the continuous subsequence from aL to aR, inclusive. Now we want to calculate the sum of mex(L,R) for all 1 <= L <= R <= n.
Input
The input contains at most 20 test cases.
For each test case, the first line contains one integer n, denoting the length of sequence.
The next line contains n non-integers separated by space, denoting the sequence.
(1 <= n <= 200000, 0 <= ai <= 10^9)
The input ends with n = 0.
Output
For each test case, output one line containing a integer denoting the answer.
Sample Input
3
0 1 3
5
1 0 2 0 1
0
Sample Output
5
24
Hint
For the first test case:
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.
解题思路:
思路很巧妙,我想不到,所以不说推理过程了。
暂时先更新线段树版本的,还有一个是dp递推版本的,还没吃透。
首先,总共是n2个数据,如果每个单独求和,时间肯定超了,而且这还不算计算每个数据时所花的时间;也开不了二维数组。
肯定需要i从1-n的遍历,然后在这个遍历里面运用logn的算法,计算出来每个i所对应的和。
i可以是区间的第一个数的位置或者最后一个数的位置或者别的。
把数据想象成右上角的三角形。
如果是最后一个数的话,那么就是一列一列从右往左的递推;如果是第一个数的话,那就是一行一行从上往下的递推。
这里只考虑i表示第一个数,mex[i]表示这一行里,行头到i这个位置的mex值,因为初始是第一行,所以可以先理解为是1-i的mex值。
计算每一行的和,在logn时间里,而且到下一行的时候mex要更新,需要维护,所以很显然想到了线段树维护区间和。
接下来就是线段树很常规的步骤了,建树得有儿子吧,所以先求出第一行,就是mex[1]——mex[n],这里可以用set,map来做,需要一个now标记,从0开始,每次循环看一下now这个值有没有在容器里面,没有的话那么mex[i]的值就是now;有的话就now++,只到上一步。
答案就是把这一行的值加入,然后维护mex,继续上一步。
求和很简单,维护要怎么维护呢?
首先mex[i]的数在一行里是越来越来多的,这就导致了mex[i]是单调不减的。每次到了下一行,相当去减去了行头,也就是a[i] -(和代码里的a[i]不太一样)。
-
mex[i]<a[i]
a[i]少了并不影响mex[i]的值
-
mex[i]>=a[i]
- 如果在这一行里面,从行头到i,有a[i],那么删去无所谓。
- 无a[i],那删去了a[i]这里面就不存在a[i]了,而mex[i]本来就比a[i]大,那肯定要改成a[i]了。
(等于的时候可改可不改,无所谓)
而刚好mex是递增的!所以找到p:mex[p]是第一个大于(等于)a[i]的;q:是a[i]下一个出现的位置,如果没有就改成n+1;所以我们只要修改p——q-1的mex为a[i]其他不动,这一行就维护好了。
求p,因为是递增了,就直接线段树二分就行,在维护一个区间最大(最小)值即可。
求q,我是采用离散化然后循环从n—1,更新当前的位置上的值出现的位置和记录这个位置上得值上一次出现的位置(通过之前前一个步骤所做的),具体看代码。
好像没有问题了,我也是这么觉得,但是因为找p是树上二分,我写的find太简单了,导致无法知道这个p有没有越界,有越界是得continue的,但如果没判断越界很容易出bug,所以我就比了一下最大值,不要学我。
代码实现:
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
#define ls (x<<1)
#define rs (x<<1|1)
#define N 200001
typedef long long LL;
int a[N],pre[N],Next[N],bin[N],mex[N];
int tag[N<<2],Max[N<<2];
int M;
LL sum[N<<2];
void update(int x){
sum[x]=sum[ls]+sum[rs];
Max[x]=max(Max[ls],Max[rs]);
}
void build(int l,int r,int x){
tag[x]=-1;
if(l==r){
sum[x]=Max[x]=mex[l];
return ;
}
int mid=(l+r)>>1;
build(l,mid,ls);
build(mid+1,r,rs);
update(x);
}
void down(int l,int r,int x){
if(tag[x]!=-1){
int mid=(l+r)>>1;
tag[ls]=tag[rs]=tag[x];
sum[ls]=(mid-l+1)*tag[x];
sum[rs]=(r-mid)*tag[x];
Max[ls]=Max[rs]=tag[x];
tag[x]=-1;
}
}
LL query(int A,int B,int l,int r,int x){
if(A<=l&&B>=r){
return sum[x];
}
down(l,r,x);
int mid=(l+r)>>1;
LL ret=0;
if(A<=mid){
ret+=query(A,B,l,mid,ls);
}
if(B>=mid+1){
ret+=query(A,B,mid+1,r,rs);
}
return ret;
}
int find(int v,int l,int r,int x){
if(l==r){
return r;
}
down(l,r,x);
int mid=(l+r)>>1;
if(Max[ls]>v){
return find(v,l,mid,ls);
}else{
return find(v,mid+1,r,rs);
}
}
void modify(int A,int B,int v,int l,int r,int x){
if(A<=l&&B>=r){
tag[x]=v;
Max[x]=v;
sum[x]=v*(r-l+1);
return ;
}
down(l,r,x);
int mid=(l+r)>>1;
if(A<=mid){
modify(A,B,v,l,mid,ls);
}
if(B>=mid+1){
modify(A,B,v,mid+1,r,rs);
}
update(x);
}
int main(){
int n;
int now;
int cnt;
int lpos,rpos;
LL ans;
while(cin>>n&&n){
now=0;
ans=0;
M=0;
set<int> st;
for(int i=1;i<=n;i++){
cin>>a[i];
bin[i]=a[i];
}
cnt=n;
sort(bin+1,bin+cnt+1);
cnt=unique(bin+1,bin+cnt+1)-bin-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(bin+1,bin+cnt+1,a[i])-bin;
}
for(int i=1;i<=n;i++){
pre[i]=n+1;
}
for(int i=n;i;i--){
Next[i]=pre[a[i]];
pre[a[i]]=i;
}
for(int i=1;i<=n;i++){
st.insert(bin[a[i]]);
while(1){
if(st.find(now)!=st.end()){
now++;
}else{
break;
}
}
mex[i]=now;
}
build(1,n,1);
for(int i=1;i<=n;i++){
ans+=query(i,n,1,n,1);
lpos=find(bin[a[i]],1,n,1);
rpos=Next[i];
M=query(n,n,1,n,1);
if(bin[a[i]]>M){
continue;
}
modify(lpos,rpos-1,bin[a[i]],1,n,1);
}
cout<<ans<<endl;
}
return 0;
}