自然数 线段树

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值