1 条题解

  • 0
    @ 2026-10-27 6:55:30

    思路与算法

    人员有独立编号,耐心相同也不能合并成一个排列元素。令E[f]为耐心严格大于f的人数。若前面已有f次失败,今天题目位为1,则录用条件就是当前人员属于E[f];位为0时必定失败。要求至少m次成功,等价于总失败数F≤n-m。

    逆序处理天数,用a[f][j]记录有符号方案系数:f是尚未逆向消去的前缀失败数,j是已在处理过的后缀中显式匹配到耐心条件上的人员数。对每个可能F初始化a[F][0]=1。逆向处理第pos天(从0计):位0只从f转到f-1,不显式限制今天的人;位1成功时转到(f,j+1),系数乘E[f]-j;位1失败时展开“全部人员减去满足录用条件人员”,分别转到(f-1,j)并加原系数、转到(f-1,j+1)并减原系数乘E[f-1]-j。无效f或可选人数不足时不转移。

    处理完全部天数后仅取f=0。每个j已确定j个不同人员的约束匹配,其余n-j个不同人员可任意填入未限制位置,贡献a[0][j]·(n-j)!。全程模998244353,用两个二维数组滚动。

    正确性说明

    固定一条成功失败轨迹,成功日的指示函数是“该人员耐心超过此前失败数”;位1的失败日指示函数为1减去同一条件的指示函数,位0的失败日为1。逐日逆序展开这些乘积,恰好得到程序的三类带符号转移,容斥不会改变原轨迹的计数。

    逆序时,已处理后缀的失败阈值不小于当前阈值,故后缀显式匹配条件的合格人员集合均包含于当前E集合。已有j个显式选定的不同人员必然都在当前集合里,因此新的显式匹配有E[f]-j或E[f-1]-j种,身份排斥仅需j而不必记录人员集合。剩余无限制位置的双射填法恰为(n-j)!。

    每个完整人员排列确定唯一的成功失败轨迹。初始化恰枚举所有F≤n-m,并由逆推f=0保证轨迹自洽;故最终每个符合至少m次成功的排列计1,其他排列计0。

    复杂度

    人数频率、E数组及阶乘初始化O(n),滚动二维数组清零O(n²)。n轮中枚举O(n²)个(f,j)状态,总时间O(n³),空间O(n²),并包含每轮清零成本。n≤500,乘法用64位中间值后取模。

    易错点

    录用条件是c_i>f,不能写成≥。位0无论耐心多少都失败;位1失败须用补集并保留负号。阈值嵌套性依赖逆序,不能直接把相同状态前向转移。初始枚举的是失败数F,而不是录用数。相同耐心者仍有不同身份,末尾必须乘剩余阶乘。公开样例已用前向人员子集DP独立核验,自编大数据使用超几何与负超几何计数,不是用本标程生成答案。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    const int MOD = 998244353;
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int n, m;
        string s;
        cin >> n >> m >> s;
        vector<int> freq(n + 1), eligible(n + 1);
        for (int i = 0, c; i < n; i++) {
            cin >> c;
            freq[c]++;
        }
        int total = n;
        for (int f = 0; f <= n; f++) {
            total -= freq[f];
            eligible[f] = total;
        }
        if (count(s.begin(), s.end(), '1') < m) {
            cout << 0 << '\n';
            return 0;
        }
        int width = n + 2;
        vector<int> a(width * width), b(width * width);
        for (int f = 0; f <= n - m; f++)
            a[f * width] = 1;
        auto add = [](int &x, int y) {
            x += y;
            if (x >= MOD)
                x -= MOD;
        };
        int done = 0;
        for (int pos = n - 1; pos >= 0; pos--, done++) {
            fill(b.begin(), b.end(), 0);
            int maxf = min(n - m, pos + 1);
            if (s[pos] == '0') {
                for (int f = 1; f <= maxf; f++)
                    for (int j = 0; j <= done; j++)
                        b[(f - 1) * width + j] = a[f * width + j];
            } else
                for (int f = 0; f <= maxf; f++)
                    for (int j = 0; j <= done; j++) {
                        int v = a[f * width + j];
                        if (!v)
                            continue;
                        if (f <= pos && eligible[f] > j)
                            add(b[f * width + j + 1], (long long)v * (eligible[f] - j) % MOD);
                        if (f) {
                            add(b[(f - 1) * width + j], v);
                            if (eligible[f - 1] > j) {
                                int z = (long long)v * (eligible[f - 1] - j) % MOD;
                                add(b[(f - 1) * width + j + 1], z ? MOD - z : 0);
                            }
                        }
                    }
            a.swap(b);
        }
        long long ans = 0, fact = 1;
        vector<int> fac(n + 1, 1);
        for (int i = 1; i <= n; i++)
            fac[i] = (long long)fac[i - 1] * i % MOD;
        for (int j = 0; j <= n; j++)
            ans = (ans + (long long)a[j] * fac[n - j]) % MOD;
        cout << ans << '\n';
    }
    
    • 1

    信息

    ID
    2043
    时间
    1000ms
    内存
    512MiB
    难度
    提高+
    标签
    递交数
    0
    已通过
    0
    上传者