[线段树] Codeforces 1436E Complicated Computations
题目大意
给定一个长为 \(n(n\leq 10^5)\) 的数列 \(\{a_n\}\),其中每个数 \(a_i\) 都在 \(1\sim n\) 之中。求这个数列所有连续子数列的\(\mathrm{mex}\)值的 \(\mathrm{mex}\)。
题解
想了好久,想了个巨难写的做法,太菜了。
首先把数列中的所有数从小到大排序,相同的数按下标从小到大排序。然后我们按 \(1\sim n\) 枚举 \(x\),看 \(x\) 是否能成为子数列 \(\mathrm{mex}\) 值的 \(\mathrm{mex}\),即是否存在某个连续子数组的 \(\mathrm{mex}\) 是 \(x\),如果存在,则 \(x\) 不是子数列 \(\mathrm{mex}\) 值的 \(\mathrm{mex}\),继续枚举 \(x\)。
考虑怎么判断是否存在某个连续子数组的 \(\mathrm{mex}\) 是 \(x\),不妨把原数组中的 \(x\) 全部删去,原数组就分成若干段,若其中某一段存在 \(1\sim x-1\) 中的所有数,则该段的 \(\mathrm{mex}\) 是 \(x\)。我们可以维护两个支持区间加的树状数组 \(\mathrm{Left}\) 和 \(\mathrm{Right}\),\(\mathrm{Left}\) 维护每一个位置上的数所处段的左端点,\(\mathrm{Right}\) 维护每一个位置上的数所处段的右端点,那么在枚举到每一个 \(x\) 的同时,我们可以使用树状数组在 \(O(\log n)\) 的时间内对每一段进行分裂和合并。然后可以使用主席树对当次进行过分裂和合并过的段查询 \(\mathrm{mex}\),时间复杂度 \(O(n\log n)\)。
但是这样非常难写,其实有更好的做法。
建立一棵线段树,我们直接从左到右扫一遍原数组,当扫描到 \(a_m\) 时,线段树上第 \(i\) 个位置维护 \(a_1\sim a_{m-1}\) 中等于 \(i\) 的数最后一次出现的位置,设 \(x\) 为线段树上 \(1\sim a_{m-1}\) 中的最小值,即最后出现的位置最早的数的位置,若 \(x\) 大于上一个出现的 \(a_{m}\) 出现的位置,则说明从上一个 \(a_{m}\) 出现的位置到本次 \(a_{m}\) 出现的位置之间的这一段中,数 \(1\sim a_{m-1}\) 均有出现,即 \(a_m\) 是该段连续子数组的 \(\mathrm{mex}\)。所以只需要一个支持单点修改和区间最小值查询的线段树即可。时间复杂度 \(O(n\log n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define RG register int
#define LL long long
template<typename elemType>
inline void Read(elemType &T){
elemType X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
T=(w?-X:X);
}
const int maxn=100010;
struct SegmentTree{
int T[maxn<<2];
void Update(int Root,int L,int R,int pos,int val){
if(L==R){T[Root]=val;return;}
int mid=(L+R)>>1;
if(pos<=mid) Update(Root<<1,L,mid,pos,val);
else Update(Root<<1|1,mid+1,R,pos,val);
T[Root]=min(T[Root<<1],T[Root<<1|1]);
}
int Query(int Root,int L,int R,int QL,int QR){
if(QR<L || R<QL) return 1<<30;
if(QL<=L && R<=QR) return T[Root];
int mid=(L+R)>>1;
return min(Query(Root<<1,L,mid,QL,QR),Query(Root<<1|1,mid+1,R,QL,QR));
}
};
SegmentTree Tree;
int data[maxn];
bool mex[maxn];
int N;
int main(){
Read(N);
bool flag=true;
for(int i=1;i<=N;++i){
Read(data[i]);
if(data[i]!=1) flag=false;
}
if(flag){printf("1\n");return 0;}
mex[1]=true;
for(int i=1;i<=N;++i){
if(data[i]==1){Tree.Update(1,1,N+1,data[i],i);continue;}
if(Tree.Query(1,1,N+1,1,data[i]-1)>Tree.Query(1,1,N+1,data[i],data[i])) mex[data[i]]=true;
Tree.Update(1,1,N+1,data[i],i);
}
for(int i=2;i<=N+1;++i)
if(Tree.Query(1,1,N+1,1,i-1)>Tree.Query(1,1,N+1,i,i)) mex[i]=true;
for(int i=1;i<=N+2;++i)
if(!mex[i]){printf("%d\n",i);break;}
return 0;
}