全区间MEX问题

全区间MEX问题

补 CF2084E 的时候,产生了这样的思考,为什么能想到这么做?所以顺便写下了这篇文章。

这里先从简单的问题引入。

P1

给定一个 \([0,n-1]\) 的排列,询问所有子区间的 \(\text{MEX}\) 的和。

形式化的,求 \(\sum_{L=1}^n\sum_{R=L}^n\text{MEX}_{[l,r]}\)

暴力是 \(O(n^3)\) 的,稍微差一点的实现是 \(O(n^3\log n)\),不太可接受,想一下这种全区间问题,十有八九是都去拆贡献做,然而单个元素的贡献可拆,这里的 \(\text{MEX}\) 是由区间里面的数共同决定的,怎么去拆?这就是难点。

暴力

结合暴力计算 \([l,r]\)\(\text{MEX}\) 的方法如下:

inline int calc(int l,int r,vector<int> &a)
{
    vector<int> tmp;
    for(int i=l;i<=r;++i)tmp.push_back(a[i]);
    sort(tmp.begin(),tmp.end());
    int now=0;
    for(int v:tmp)
    {
        if(v!=now)return now;
        now++;
    }
    return now;	
}

Trick

抽象化一下这个计算过程,发现对于一个区间 \([l,r]\),我们循环 \(now\)\(0\)\(n\) 依次检查 \(now\) 是否在 \([l,r]\) 之间出现,如果出现就自加,否则就直接 \(\text{return}\)。这里就有一个非常重要的等价转化,也就是 :

\[ans=\sum_{k=1}^n [0\text{到}k-1都在[l,r]中出现过] \]

意味着我们得到了一个可以拆散了计算,但是最后得到的结果相同的方法,这就可以拆贡献了。

直接枚举 \(k\) ,然后把 \([0,k-1]\) 的所有数添加到当前的子区间里面,用双指针记录扩展到的 \([l,r]\) ,那么到当前扩展完毕,对答案的贡献就是 : \(l\times (n-r+1)\) 。这可以保证所有包含了 \([0,k-1]\) 所有数的所有子区间在当前情况下的贡献 \(1\) 都被不重不漏地加到了总答案里面。

Code

#include<bits/stdc++.h>
using namespace std;
//计算全区间的MEX和 给定一个0!~n-1排列的情况下
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int n;
    cin>>n;
    vector<int> a(n+1);
    for(int i=1;i<=n;++i)cin>>a[i];

    long long ans=0;
    vector<int> pos(n+1);
    for(int i=1;i<=n;++i)pos[a[i]]=i;
    int l=n+1,r=0;
    for(int k=0;k<n;++k)
    {
        while(l>pos[k])l--;
        while(r<pos[k])r++;
        ans+=1ll*l*(n-r+1);
        // cout<<l<<' '<<r<<'\n';
    }
    cout<<ans;

    return 0;
}

P2

CF2084E

同样给定一个 \([0,n-1]\) 的排列,但是其中有些数被挖掉了,用 \(-1\) 表示。

求的东西大差不差,也就是:在所有能够使得整个序列最终是一个排列的填数方案中,求出其全区间 \(\text{MEX}\) 和。

最后的答案就是所有可能情况的 \(\text{MEX}\)之和

分析

不妨沿用上面的思考,仍在最外层枚举一个这样的 \(k\) ,在添加数字并且扩展区间的时候,会发生有一些数字并没有出现而是 \(-1\) 的情况,这不要紧,这种情况我们不扩展即可。

也就是说,当前维护的区间是 所有在总序列中出现了的,被当前的k所要求的数,但这时,贡献不再是简单的 \(l\times (n-r+1)\) ,而是和组合数有关,并且和需要填的缺失的数字的个数有关。

假设随手记录下来的需要填的缺失数字个数是 \(fil\) ,那么能产生贡献的区间需要满足两个条件:

  1. 区间 \([L,R]\) 满足其完全包含 \([l,r]\)
  2. 记区间 \([L,R]\) 中有 \(cnt\)\(-1\) ,那么必须有 \(cnt\ge fil\)

如果以上两个条件都满足,那么一个区间所能产生的所有情况下的贡献就是 (记整个区间的 \(-1\) 个数是 \(all\))

\[A_{cnt}^{fil}\times (all-fil)! \]

我们无法接受在枚举了 \(k\) 的情况下再去 \(O(n^2)\) 地枚举区间,实际上,我们关心的就只是:当前合法的区间中 各包含了 \(i\in[fil,n]\)\(-1\) 的区间的个数。

我们一开始 \(O(n^2)\) 预处理出所有 \(i\in[0,n]\)\(-1\) 所对应的区间个数,用 \(cnt[i]\) 表示。

在区间扩展的过程中,我们动态地删除不满足条件的区间,最后再枚举 \(cnt\) 来统计答案即可。

这里删除区间的操作其实很有学问,可以用数学归纳法来证明正确性,建议细品。

#include<bits/stdc++.h>
using namespace std;
const int N=5e3+10;
typedef long long ll;
const ll MOD=1e9+7;
inline ll power(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return ans%MOD;
}
ll jc[N],ijc[N];
inline ll A(ll n,ll m){if(n<m)return 0;return jc[n]*ijc[n-m]%MOD;}
inline void solve()
{
    int n;
    cin>>n;
    vector<int> a(n+5),s(n+5,0),pos(n+5,0);
    vector<ll> cnt(n+5,0);
    ll all=0;
    for(int i=1;i<=n;++i)cin>>a[i];
    for(int i=1;i<=n;++i)
    {
        s[i]=s[i-1]+(a[i]==-1),all+=a[i]==-1;
        if(a[i]!=-1)pos[a[i]]=i;
    }
    for(int i=1;i<=n;++i)
        for(int j=i;j<=n;++j)
            cnt[s[j]-s[i-1]]++,assert(s[j]-s[i-1]>=0);

    ll ans=0,fil=0;
    int r=0,l=n+1;
    for(int k=0;k<n;++k)
    {
        if(pos[k])
        {
            while(l>pos[k])
            {
                if(l<=n)for(int R=max(l,r);R<=n;++R)cnt[s[R]-s[l-1]]--,assert(l-1>=0);    
                l--;
            }
            while(r<pos[k])
            {
                if(r>=1)for(int L=min(l,r);L>=1;--L)cnt[s[r]-s[L-1]]--,assert(L-1>=0);
                r++;
            }//***1
        }
        else fil++;
        for(int hole=fil;hole<=n;++hole)
            ans+=cnt[hole]*A(hole,fil)%MOD*jc[all-fil]%MOD,ans%=MOD,assert(all>=fil);//**2
    }
    cout<<ans<<'\n';
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    jc[0]=1;
    const int lim=5000;
    for(int i=1;i<=lim;++i)jc[i]=1ll*jc[i-1]*i%MOD;
    ijc[lim]=power(jc[lim],MOD-2);
    for(int i=lim-1;i>=0;--i)ijc[i]=1ll*ijc[i+1]*(i+1)%MOD;
    int T;cin>>T;
    while(T--)solve();
    return 0;
}
posted @ 2025-04-10 10:48  Hanggoash  阅读(126)  评论(0)    收藏  举报
动态线条
动态线条end