自然數 線段樹

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章