CF1608F MEX counting 题解

题解:CF1608F MEX counting

与其他题解不同,本篇题解是运用辅助数组 \(g\) 来解决问题。虽然代码可能要繁琐一点,但是辅助数组的思路适用范围更广一点。


首先还是转化为前 \(i\) 个数的 \(mex\) 在区间 \([l_i,r_i]\) 内。

我们用 dp 数组 \(f_{i,x,c}\) 表示处理到了第 \(i\) 个数,当前的 mex 为 \(x\),大于 mex 一共有 \(c\) 个不同的数。这里我们并不关心大于 mex 的数具体是哪些,而只关心有多少个,因为只要满足大于 mex 一共有 \(c\) 个不同的数,方案数都是一样的,我们只需要记录这个一样的方案数即可。注意这里钦定了这 \(c\) 个数的顺序,即这 \(c\) 个数是有序的(看不懂的话可以根据下面的转移来理解)。

根据定义,考虑有哪些方式能转移到 \(f_{i,x,c}\)

  • 这个位置填了一个之前已经出现过的数,这样的数共有 \(x+c\) 个,所以转移为 \(f_{i-1,x,c}*(x+c)\rightarrow f_{i,j,k}\)
  • 这个位置填了大于 mex 且之前没有出现过的数,那肯定是由 \(f_{i,x,c-1}\) 转移过来,因为原先有 \(c-1\) 个数,而数是有序的,所以新加的数与这 \(c-1\) 个数的相对顺序共有 \(c\) 种,所以转移为 \(f_{i,x,c-1}*c\rightarrow f_{i,j,k}\)
  • 第三种情况也是本题的核心,即这个位置填了 mex,这样子我们并不知道有哪些状态能更新到。这时就需要辅助数组 \(g\),用 \(g_{i,x,c}\) 表示填到了第 \(i\) 个数,小于 \(x\) 的数都出现过,大于等于 \(x\) 共有 \(c\) 个不同的数。

这样 \(g\) 怎么辅助数组 \(f\) 来转移呢?具体流程为,先处理前两个转移,同时更新数组 \(g\),然后再用 \(g\) 来更新 \(f\) 的第三个转移。

  • 对于 \(f\)\(g\) 的转移,即这个位置填了 mex,显然有 \(f_{i,x,c}\rightarrow g_{i,x+1,c}\)
  • 对于 \(g\)\(f\) 的转移,那么对 \(g_{i,x,c}\) 分两种情况,即是否出现了 \(x\) 这个数。如果出现了,那么应该转移 \(g_{i,x,c}\rightarrow g_{i,x+1,c-1}\),如果没有出现,那么应该转移 \(g_{i,x,c} \rightarrow f_{i,x,c}\)

最后统计答案,对于 \(f_{n,x,c}\),由于我们没有关心这 \(c\) 个数具体是啥,所以要在剩下的 \(n-x\) 个数中选 \(c\) 个,因为已经钦定了 \(c\) 个数的顺序,所以 \(f_{n,x,c}\) 对答案的贡献为 \(f_{n,x,c}*\binom{n-x}{c}\)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 2005,mod = 998244353;
int l[N],r[N],n,k;
ll C[N][N],f[2][N][N],g[N][N],ans;
void init()
{
    f[0][0][0] = C[0][0] = 1;
    for(int i = 1;i <= n;i++)for(int j = 0;j <= i;j++)
        C[i][j] = ((j?C[i-1][j-1]:0)+C[i-1][j])%mod;
}
inline int rd()
{
    char c;int f = 1;
    while(!isdigit(c = getchar()))if(c=='-')f = -1;
    int x = c-'0';
    while(isdigit(c = getchar()))x = x*10+(c^48);
    return x*f;
}
int main()
{
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    n = rd();k = rd();init();
    for(int i = 1;i <= n;i++)
    {int x = rd();l[i] = max(l[i-1],x-k);r[i] = min(x+k,n);}
    for(int i = 1;i <= n;i++)
    {
        for(int x = l[i-1];x <= r[i];x++)for(int c = 0;c <= n;c++)
            g[x][c] = 0;
        for(int x = l[i-1];x <= r[i-1];x++)for(int c = 0;c <= n;c++)
            f[1][x][c] = f[0][x][c],f[0][x][c] = 0;
        for(int x = l[i-1];x <= r[i-1];x++)for(int c = 0;c <= n;c++)//mex=x,cnt(1)=c
        {
            if(l[i] <= x&&x <= r[i])
            {
                (f[0][x][c] += f[1][x][c]*(x+c)) %= mod;
                if(c)(f[0][x][c] += f[1][x][c-1]*c) %= mod;
            }
            (g[x+1][c] += f[1][x][c]) %= mod;
        }
        for(int x = l[i-1];x <= r[i];x++)for(int c = 0;c <= n;c++)
        {
            if(c)(g[x+1][c-1] += g[x][c]) %= mod;
            if(l[i] <= x&&x <= r[i])(f[0][x][c] += g[x][c]);
        }
    }
    for(int x = 0;x <= n;x++)for(int c = 0;c <= n;c++)
        (ans += f[0][x][c]*C[n-x][c]) %= mod;
    cout << ans;
    return 0;
}

这种设辅助数组的思路在许多题中都可以用,值得掌握一下。

posted @ 2024-07-27 16:10  max0810  阅读(77)  评论(0)    收藏  举报