1 条题解
-
0
思路与算法
人员有独立编号,耐心相同也不能合并成一个排列元素。令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