CF1436E Complicated Computations

CF1436E Complicated Computations

题目描述:

求一个数列的所有子区间的 mex 值的 mex

某个数组的 mex 是这个数组中没有包含的最小正整数。

数据范围:

\(1\leq n\leq 10^5,1\leq a_i\leq n\)

思路:

分析一下题目的流程,他先要你求出子区间的 mex,然后再求所有 mexmex
如果我们能够算出所有子区间的 mex 有哪些,则可以枚举求出 mex

所以我们考虑怎么解决第一个问题:
我们发现如果对每个子区间都去做一边 mex 显然是不优秀的,复杂度也不对。所以,我们尝试去计算有哪些区间的 mex

假设当前我们要求 \(x\) 能否作为 mex 出现,则我们不妨将整个序列通过 \(x\) 划分为若干段,若任意一段中包含的数的范围为 \(1\sim x-1\),则这一段的 mex\(x\)

考虑怎么维护这个东西。我们枚举 \(a_i\),如果 \(lst_{a_i}+1\sim i-1\) 这一段中包含了 \(1\sim a_i-1\),则这一段的 mex\(a_i\)。换言之,就是如果 \(1\sim a_i-1\) 最后出现的位置的最小值 \(\ge lst_{a_i}+1\),则这一段的 mex\(a_i\)

所以问题转换为单点修改,区间最小值。显然可以使用线段树解决。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mem(a) memset(a,0,sizeof(a))
#define set(a,b) memset(a,b,sizeof(a))
#define ls p<<1
#define rs p<<1|1
#define pb push_back
#define pt putchar
#define All(a) a.begin(),a.end()
#define T int t;cin>>t;while(t--)
#define rand RAND
using namespace std;
char buf[1<<20],*p1,*p2;
#define gc()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
template<class Typ> Typ &re(Typ &x){char ch=gc(),sgn=0; x=0;for(;ch<'0'||ch>'9';ch=gc()) sgn|=ch=='-';for(;ch>='0'&&ch<='9';ch=gc()) x=x*10+(ch^48);return sgn&&(x=-x),x;}
template<class Typ> void wt(Typ x){if(x<0) putchar('-'),x=-x;if(x>9) wt(x/10);putchar(x%10^48);}
const int inf=0x3f3f3f3f3f;
const int maxn=1e5+5;
const int mod=1e9+7;
int seed = 19243;
unsigned rand(){return seed=(seed*48271ll)%2147483647;}
int n;
int a[maxn];
int lst[maxn];
struct node{
	int mn;
}tree[maxn<<2];
int ban[maxn];
void push_up(int p){
	tree[p].mn=min(tree[ls].mn,tree[rs].mn);
}
void update(int l,int r,int x,int k,int p){
	if(l==r){
		tree[p].mn=k;
		return ;
	}
	int mid=l+r>>1;
	if(x<=mid)update(l,mid,x,k,ls);
	else update(mid+1,r,x,k,rs);
	push_up(p);
}
int query(int l,int r,int L,int R,int p){
	if(L<=l&&R>=r){
		return tree[p].mn;
	}
	int mid=l+r>>1;
	int res=inf;
	if(L<=mid)res=min(res,query(l,mid,L,R,ls));
	if(R>mid)res=min(res,query(mid+1,r,L,R,rs));
	return res;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++){
		if(a[i]!=1)ban[1]=1;
		if(a[i]>1&&query(1,n,1,a[i]-1,1)>lst[a[i]])ban[a[i]]=1;
		lst[a[i]]=i;
		update(1,n,a[i],i,1);
	}
	for(int i=2;i<=n+1;i++){
		if(query(1,n,1,i-1,1)>lst[i])ban[i]=1;
	}
	for(int i=1;i<=n+2;i++)if(!ban[i]){
		cout<<i<<endl;
		return 0;
	}
	return 0;
}

补充:

法二:
我们常规求区间 mex 的时候一半都会使用莫队来解决这个问题。然后我们考虑人为的添加一些询问给这个题目。

则对于每个 \(a_i\),令 \(a_i\) 出现的次数分别为 \(p_1,p_2,\cdots,p_k\),则添加询问:\((1,p_1-1),(p_1+1,p_2-1),\cdots,(p_{k-1}+1,p_k-1),(p_k+1,n)\)
如果中间有一些询问的长度不够,就可以直接舍去。不难发现这个询问的次数不会超过 \(N\) 次。
所以总复杂度 \(O(N\sqrt N\log N)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=1e5+5;
int n,a[maxn];
struct Query{int l,r,id,block;bool operator < (const Query& q)const{if(block==q.block)return r<q.r;else return block<q.block;}}Q[maxn];
struct BIT{int tr[maxn];int lowbit(int x){return x&-x;}void update(int x,int k){while(x<=n){tr[x]+=k;x+=lowbit(x);}}int query(int x){int res=0;while(x){res+=tr[x];x-=lowbit(x);}return res;}void clear(){memset(tr,0,sizeof(tr));}}bit;
vector<int>g[maxn];
int f[maxn];
int tot;
int cnt[maxn];
void add(int x){
    ++cnt[x];
    if(cnt[x]==1)bit.update(x,1);
}
void del(int x){
    --cnt[x];
    if(cnt[x]==0)bit.update(x,-1);
}
int get(int x){
    int t=bit.query(x);
    if(t==x-1)return 1;
    return 0;
}
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++){
        g[a[i]].push_back(i);
        if(a[i]!=1)f[1]=1;
    }
    int blo=sqrt(n);
    for(int i=2;i<=n+1;i++){
        int l=1,r=0;
        for(int x:g[i]){
            r=x-1;
            if(r-l+1>=i-1)Q[++tot]={l,r,i,(l-1)/blo+1};
            l=x+1;
        }
        r=n;
        if(r-l+1>=i-1)Q[++tot]={l,r,i,(l-1)/blo+1};
    }
    sort(Q+1,Q+tot+1);
    int l=1,r=0;
    for(int i=1;i<=tot;i++){
        int L=Q[i].l,R=Q[i].r;
        if(f[Q[i].id])continue;
        while(l>L)add(a[--l]);
        while(l<L)del(a[l++]);
        while(r<R)add(a[++r]);
        while(r>R)del(a[r--]);
        f[Q[i].id]=get(Q[i].id);
    }
    for(int i=1;i<=n+2;i++){
        if(!f[i]){
            cout<<i<<endl;
            return 0;
        }
    }
    return 0;
}

后记:

其实这个题目,无论是莫队还是线段是,都是采用的同样的思想,只不过是实现方式略有区别罢了。

posted @ 2023-11-07 22:32  Candycar  阅读(46)  评论(0)    收藏  举报