Mex
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.
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.
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 24HintFor 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.
Source
Recommend
liuyiding
题意:有一组序列a[i](1<=i<=N), 让你求所有的mex(l,r), mex(l,r)表示区间[l,r]中最小的未在序列中出现的非负整数。
那场比赛是我在做这道题,写了个O(N^2)的算法 , 不出所料地超时了。 学得还不够,没看出是线段树问题 。
很简单的累加思路,关键是怎么处理每次累加的值 。
根据题意,
我们先列出第一组序列:
mex(1,1), mex(1,2), mex(1,3), mex(1,4),....., mex(1,n) ,显然这是一个单调非降序列(很重要),同样地,我们也可以列出第二组序列。
最终的答案,就是所有的序列的和。
需要具备的思路是:
1, 第m组序列的和是可以由第m-1组序列得到的;
2, 每去掉一个原序列的值ai,后面的mex(m,k)有的是需要更新的,在ai再次出现之前的所有大于ai的mex(m,k)都需要变成ai;
3, 每去掉一个原序列的值后所得到的序列的和只需要将前面的序列的和更新即可。
具体做法:
用线段树维护mex的序列 ,储存区间的和,并需要记录区间最右边的mex值(这个值一定是这段区间的最大的mex值),然后,从1到n的区间和 sum就是序列的和;
每删掉一个ai,就需要更新从ai到下个ai出现的位置之前所有的mex值(显然,这些mex值是一定小于ai的,这就是单调性),然后还需要更新区 间和,另外,ai及其之前的mex的区间的和需要全部变成 0 。
线段树 区间更新 区间求和 二分查找(单调序列)
CODE:
/**
******线段树********
**/
#include <map>
#include <set>
#include <list>
#include <queue>
#include <stack>
#include <cmath>
#include <ctime>
#include <vector>
#include <bitset>
#include <cstdio>
#include <string>
#include <numeric>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int dx[4]= {-1,1,0,0};
int dy[4]= {0,0,-1,1}; //up down left right
#define eps 1e-8
#define inf 0x7fffffff
#define debug puts("BUG")
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define Read freopen("in.txt","r",stdin)
#define Write freopen("out.txt","w",stdout)
#define Mem(a,x) memset(a,x,sizeof(a))
#define maxn 222222
int a[maxn];
int b[maxn];
int next[maxn];
map<int,int>mp;
int n;
/****线段树****/
ll sum[maxn<<2],col[maxn<<2],mx[maxn<<2];//区间和 lazy标记(表示这段区间全部覆盖成col) 维护的序列
/*****end******/
void init()//初始化
{
mp.clear();
Mem(next, 0);
Mem(a, -1);
Mem(b, -1);
Mem(mx, -1);
Mem(col, -1);
Mem(sum, 0);
}
void read()//读入数据
{
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
if(mp[a[i]]>0)
{
next[mp[a[i]]]=i-1;//给上一个a[i]记录下个a[i](就是当前这个)出现的位置
mp[a[i]]=i;//为下个a[i]准备
}
else
{
next[mp[a[i]]]=n;//a[i]后面并无a[i],位置赋值为n,为以后的计算准备
mp[a[i]]=i;
}
}
mp.clear();
int Min=0;
if(a[1]==0) Min=1;
b[1]=Min , mp[a[1]]=1;
for(int i=2; i<=n; i++)
{
mp[a[i]]=1;//记录出现的数字
while(mp[Min]) Min++;
b[i]=Min;//求出要维护的mex序列
}
}
void PushUp(int rt)//线段树向上更新
{
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
mx[rt]=mx[rt<<1|1];//得到区间的最右边的mex值
}
void PushDown(int rt,int len)
{
int Lson=(rt<<1),Rson=(rt<<1|1);
if(col[rt]!=-1)//col标记
{
col[Lson]=col[Rson]=col[rt];
mx[Lson] =mx[Rson] =col[rt];
sum[Lson]=(ll) col[rt]*(len-(len>>1));
sum[Rson]=(ll) col[rt]*(len>>1);
col[rt]=-1;
}
}
void Build(int l,int r,int rt)//建树
{
if(l==r)
{
mx[rt]=sum[rt]=b[l];
return ;
}
int m=(l+r)>>1;
Build(lson);
Build(rson);
PushUp(rt);
}
int Find(int key,int l,int r,int rt)//根据单调性在线段树上,二分查找key值的位置
{
int len=r-l+1;
if(l==r) return l;//返回的是key在区间的位置
PushDown(rt,len);//注意要进行col的更新
int m=(l+r)>>1;
if(key<=mx[rt<<1])
return Find(key,lson);
else
return Find(key,rson);
}
void Init(int x,int l,int r,int rt)//成段更新从1到x的区间的sum值
{
int len=r-l+1;
if(l==r)
{
mx[rt]=-1;
sum[rt]= 0;//这一段是已经去掉的部分,所以sum为0
return ;
}
PushDown(rt,len);//注意
int m=(l+r)>>1;
if(x<=m)
Init(x,lson);
else
Init(x,rson);
PushUp(rt);//更新
}
void Update(int L,int R,int key,int l,int r,int rt)
{//成段更新
int len=r-l+1;
if(L<=l&&r<=R)
{
col[rt]=key;//更新col标记
sum[rt]=(ll) col[rt]*len;
mx[rt]=key;
return ;
}
PushDown(rt,len);//注意
int m=(l+r)>>1;
if(L<=m) Update(L,R,key,lson);
if(R >m) Update(L,R,key,rson);
PushUp(rt);//更新
}
void solve()
{
ll ans=0;
Build(1,n,1);
for(int i=1; i<=n; i++)
{
ans+=sum[1];//累加
Init(i,1,n,1);//将从1到i的sum去掉(就是变成0)
int r=next[i];//得到a[i]下一次出现的位置
if(r==-1||r==0) r=n;//a[i]下一次没有出现
if(mx[1]<a[i]) continue;//mx[1],是从1~n区间的最有值,是该序列最大值,如果该值都比a[i]小,说明区间不需要更新
int l=Find(a[i],1,n,1);//求出a[i]的位置
if(r>=l)
Update(l,r,a[i],1,n,1);//更新的是从a[i]到a[i]下一次出现之前的区间(也可能每没出现,没出现则需要更新到最后)
//根据单调性,a[i]的位置之前,全是比a[i]小的mex值,因此,不需要更新
}
printf("%I64d\n",ans);//这里要注意,要使用%I64d输出........
}
int main()
{
while(~scanf("%d",&n)&&n)//循环输入
{
init();
read();
solve();
}
}