E. White Magic

链接
\(首先通过观察可以很容易的发现如果一组子序列不存在0的话那么它是显然成立的\)
\(因为mex始终为0\)
\(那么我们再思考如果存在0那么该子序列就必须满足后缀的子序列的mex始终小于前缀的min\)
\(那么可能存在2个0吗 很明显不可能因为一个0在右边的话mex必然大于1 而min却必定为0\)
\(所以我们首先将所有非0的数选上 然后遍历思考0可以在哪里插入即可\)
\(通过观察可以发现前缀的min和后缀的mex很容易维护\)
\(我们只需要假设0存在然后维护后缀的mex对于前缀的min进行比较 如果存在断点即为后方都不可以成立\)
\(用c存完断点之后 遍历每个b_i的位置是否在断点出现前与a_i一一对应(确认前方是否存在可以插入的0)开头必定可插入(读者可自行思考)\)

题目解析

我们需要找到一个给定序列的最长魔法子序列。魔法子序列的定义是:对于每一个位置 $ i $(从 1 到 $ n-1 $),满足以下条件:

\[\text{min}(a_1, a_2, \dots, a_i) \geq \text{mex}(a_{i+1}, a_{i+2}, \dots, a_n) \]

其中,\(\text{mex}\) 是最小未出现的非负整数。

关键点分析

  1. MEX 的性质

    • MEX 的值总是从 0 开始递增。
    • 如果某个后缀中包含所有小于某个值 $ t $ 的数字,则 \(\text{mex} = t\)
  2. 魔法子序列的特点

    • 如果一个子序列中没有 0,那么它的 MEX 始终为 0,因此它一定是魔法子序列。
    • 如果一个子序列中有 0,那么需要保证前缀的最小值始终大于等于后缀的 MEX。
  3. 最多只能有一个 0

    • 如果有两个或更多个 0,那么在某些情况下,MEX 会大于前缀的最小值(例如,当一个 0 在右侧时,MEX 至少为 1,而前缀的最小值可能为 0)。
    • 因此,我们可以先将所有非零元素选入子序列,然后再考虑是否可以插入一个 0。
  4. 如何判断能否插入 0

    • 我们可以通过维护前后缀的信息来判断:
      • 前缀最小值:从前到后逐步计算每个前缀的最小值。
      • 后缀 MEX:从后到前逐步更新每个后缀的 MEX。
    • 如果存在某个位置 $ i $,使得前缀的最小值小于后缀的 MEX,则说明无法在此处插入 0。

算法步骤

  1. 初始化

    • 将所有非零元素提取出来形成一个新的数组 $ b $。
    • 记录每个非零元素在原数组中的位置。
  2. 维护前缀最小值和后缀 MEX

    • 从前到后计算数组 $ b $ 的前缀最小值。
    • 从后到前计算数组 $ b $ 的后缀 MEX,并检查是否存在断点(即前缀最小值小于后缀 MEX 的位置)。
  3. 判断是否可以插入 0

    • 如果没有任何断点,则可以在任意位置插入 0。
    • 如果有断点,则需要检查断点之前是否可以插入 0。
  4. 输出结果

    • 如果可以插入 0,则最长魔法子序列长度为 $ |b| + 1 $。
    • 否则,最长魔法子序列长度为 $ |b| $。

示例详解

输入样例 1

5
4 3 2 1 0
  1. 提取非零元素得到 $ b = [4, 3, 2, 1] $。
  2. 计算前缀最小值:
    • 前缀最小值为 $ [4, 3, 2, 1] $。
  3. 计算后缀 MEX:
    • 从后往前逐步更新 MEX:
      • 最后一个元素 $ [1] $ 的 MEX 为 0。
      • 最后两个元素 $ [2, 1] $ 的 MEX 为 0。
      • 最后三个元素 $ [3, 2, 1] $ 的 MEX 为 0。
      • 所有元素 $ [4, 3, 2, 1] $ 的 MEX 为 0。
  4. 检查断点:
    • 前缀最小值始终大于等于后缀 MEX,因此没有断点。
  5. 插入 0:
    • 可以在任意位置插入 0,因此最长魔法子序列长度为 $ |b| + 1 = 5 $。

输出样例 1

5

复杂度分析

  1. 时间复杂度

    • 对于每个测试用例,我们需要遍历数组两次(一次从前到后,一次从后到前),时间复杂度为 $ O(n) $。
    • 总共有 $ t $ 个测试用例,且所有测试用例的 $ n $ 之和不超过 $ 2 \times 10^5 $,因此总时间复杂度为 $ O(2 \times 10^5) $。
  2. 空间复杂度

    • 主要使用了几个辅助数组(如 $ b $、前缀最小值数组、后缀 MEX 数组等),空间复杂度为 $ O(n) $。

完整代码

以下是基于上述思路的完整代码实现:

#include<bits/stdc++.h>
#define int long long
#define all(x) (x).begin(),(x).end()
#define rall(x) (x).rbegin(),(x).rend()
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int mod=998244353;
int gcd(int a,int b){return b?gcd(b,a%b):a;};
int qpw(int a,int b){int ans=1;while(b){if(b&1)ans=ans*a%mod;a=a*a%mod,b>>=1;}return ans;}
int inv(int x){return qpw(x,mod-2);}
void solve(){
    int n;cin>>n;
    vector<int>a(n+1);
    vector<int>b;
    vector<int>mp(n+1);
    for (int i=1;i<=n;i++) {
        cin>>a[i];
        if (a[i]!=0)b.pb(a[i]),mp[b.size()-1]=i-1;
    }
    vector<int>vis(n+2,0);
    vector<int>mi(b.size()+1,1e18);
    int mex=1;
    // return;
    vector<int>c(b.size()+1);
    for (int i=0;i<b.size();i++) {
        mi[i]=min(mi[max(0ll,i-1)],b[i]);
    }
    int ok=1;
    for (int i=b.size()-1;i>=1;i--) {
        if (b[i]<=n)vis[b[i]]++;
        while (vis[mex])mex++;
        if (mi[i-1]>=mex)c[i]=1;else ok=0;
    }
    int len=b.size();
    // return;
    if (ok==1&&b.size()!=n) {
        cout<<len+1<<'\n';return;
    }
    for (int i=1;i<b.size();i++) {
        if (mp[i-1]!=i-1) {
            cout<<len+1<<'\n';return;
        }
        if (c[i]==0)break;
    }
    cout<<len<<'\n';
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
    int _=1;
    cin>>_;
    while(_--)solve();
}
---
posted @ 2025-04-08 15:02  archer2333  阅读(37)  评论(0)    收藏  举报