自然数 线段树

来源:互联网 发布:杭州淘宝运营学徒招聘 编辑:程序博客网 时间:2024/06/05 02:27
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 200005using 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;}

原创粉丝点击