HDU 4747 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.

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值