1 条题解
-
0
F2. 小排列问题(困难版)
详细题解
一、问题转化
将排列 看作 网格上的 个棋子,第 行的棋子在列 。
条件 表示:在子网格 (前 行且前 列)中恰好有 个棋子(若 )。如果 且 ,则不可能满足条件(因为整个 网格中恰好有 个棋子,且都在前 行前 列),答案为 。
如果 ,我们可以强制 ,因为整个网格必须恰好包含所有 个棋子,这个条件自动成立。
因此,在输入中若 ,我们将其改为 ;若 为其他值且不等于 ,直接输出 。二、必要条件
考虑所有 的位置,设它们为 ,并补充 ,。
$$0 = a_{i_0} \le a_{i_1} \le a_{i_2} \le \dots \le a_{i_m} = n, $$
由于子网格 包含 ,因此必须有:且每个 。
若上述非递减条件不成立,答案为 。三、组合计数
对于相邻的两个非 位置 和 ,已知在 中已有 个棋子。
我们可以在不改变计数的情况下,将这些棋子重新标号,使得它们恰好占据前 行和前 列(即位置 )。
现在需要再放入 个新棋子,使得子网格 中总共有 个棋子。新棋子必须放在 中尚未被占用的格子。
未被占用的行:共有 行,但前 行已被占用(每行已有一个棋子),不过这些行中仍有空列(列 )可以放新棋子,因此所有 行都是可用的。
未被占用的列:前 列已被占用,但列 到 是空的,共 列。
实际上,更准确的分析是:新棋子的行可以来自所有 行,列只能来自后 列?不对,因为新棋子也可以放在前 行的后 列中。
经典结论(可通过组合对射证明):在已有 个棋子占据前 行和前 列的前提下,在 中再放 个棋子使得每行每列至多一个棋子的方案数为:解释:从剩余的空闲行(共 行)中选出 行,从空闲列(共 列)中选出 列,然后将这 行与 列一一匹配( 种方式)。
注意:这里“空闲行”是指前 行之外的行吗?实际上,前 行已经有一个棋子,但它们还可以再放新棋子吗?不行,因为每行只能有一个棋子。所以前 行不能再放新棋子。因此空闲行正是 到 这 行。空闲列也是 到 这 列。而新棋子必须同时位于空闲行和空闲列的交集(一个 的正方形)中。因此从该正方形中选 行 列再匹配,就是 。
如果允许新棋子放在前 行但后 列,那么就会与已有棋子冲突(同行),所以不行。因此上述解释正确。四、最终公式
对所有相邻的非 位置(包括虚拟的 )乘以上述值,得到答案:
$$\text{ans} = \prod_{k=1}^{m} \left( \binom{i_k - a_{i_{k-1}}}{a_{i_k} - a_{i_{k-1}}}^2 \cdot (a_{i_k} - a_{i_{k-1}})! \right) \pmod{998244353}. $$五、算法实现
- 预处理阶乘 和逆元 到 ,用于快速计算组合数。
- 对于每个测试用例:
- 读入 和数组 (下标从 开始)。
- 若 且 ,输出 并跳过。
- 否则令 。
- 初始化 (上一个非 的 值),。
- 遍历 到 :
- 若 ,跳过。
- 若 或 ,则答案 并结束。
- 令 ,。
- $ans = ans \times \binom{free}{d}^2 \times fact[d] \pmod{MOD}$。
- 更新 。
- 输出 。
六、时间复杂度
每个测试用例 ,总复杂度 ,满足题目限制。
C++ 代码实现
#include <bits/stdc++.h> using namespace std; const int MOD = 998244353; const int MAXN = 200005; long long fact[MAXN], invfact[MAXN]; long long modpow(long long a, long long e) { long long res = 1; while (e) { if (e & 1) res = res * a % MOD; a = a * a % MOD; e >>= 1; } return res; } void precompute() { fact[0] = 1; for (int i = 1; i < MAXN; i++) fact[i] = fact[i-1] * i % MOD; invfact[MAXN-1] = modpow(fact[MAXN-1], MOD-2); for (int i = MAXN-2; i >= 0; i--) invfact[i] = invfact[i+1] * (i+1) % MOD; } long long C(int n, int k) { if (k < 0 || k > n) return 0; return fact[n] * invfact[k] % MOD * invfact[n-k] % MOD; } void solve() { int n; cin >> n; vector<int> a(n+1); for (int i = 1; i <= n; i++) cin >> a[i]; if (a[n] != -1 && a[n] != n) { cout << "0\n"; return; } a[n] = n; // 强制最后一个为 n long long ans = 1; int prev = 0; // 上一个非 -1 的 a 值 for (int i = 1; i <= n; i++) { if (a[i] == -1) continue; if (a[i] < prev || a[i] > i) { cout << "0\n"; return; } int d = a[i] - prev; int free = i - prev; ans = ans * C(free, d) % MOD * C(free, d) % MOD * fact[d] % MOD; prev = a[i]; } cout << ans << '\n'; } int main() { ios::sync_with_stdio(false); cin.tie(0); precompute(); int t; cin >> t; while (t--) solve(); return 0; }
- 1
信息
- ID
- 6447
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者