1 条题解
-
0
题解:CF 1909F1 - 小排列问题(简单版本)
问题重述
给定长度为 的数组 ,其中 。
求满足下列条件的 的排列 的数量:对于每个 ,前缀 中值不超过 的元素个数恰好等于 。
答案对 取模。
关键观察
-
必要条件
- 整个排列中所有 个数都不超过 ,因此 必须等于 。
- 必须是非递减的,并且 。
- 相邻差值 (规定 )只能为 或 。
理由:考虑从 到 ,新加入的区域是第 行和第 列(交点为 )。该区域最多能容纳 个新点(因为每行每列只能有一个点)。
-
网格模型
将排列 视为 网格上的 个点,坐标为 。
那么条件“前缀 中值 的个数为 ”等价于:在左上角 的子网格中,恰好有 个点。
设 ,我们已经在前 的区域内放置了 个点。
由于排列的性质,这 个点占据了 行和 列。我们可以将这些行/列重新编号为 ,即认为它们已经占据了左上角 的方块(不影响计数)。 -
空闲行列数
在 步后,已经占用了 行和 列,所以剩下的空闲行和空闲列各有:
这些空闲行/列分别对应编号 到 。
第 步的转移
第 步新增的可放置区域由三部分组成:
- 第 行前 列:其中只有 列是空闲的(因为被占用的列不能放新点)。
- 第 列前 行:其中只有 行是空闲的。
- 格子 :总是空闲的。
令 。根据 的值,我们有三种情况:
- :不添加任何点,方案数乘 。
- :恰好添加一个点。有三种选择:
- 放在 : 种。
- 放在第 行的某个空闲列: 种。
- 放在第 列的某个空闲行: 种。
总方案数为 。
- :必须添加两个点,一个在第 行(非交点),一个在第 列(非交点)。
设第 行的点在第 列,第 列的点在第 行,其中 和 必须从 个空闲行/列中选择。
任意有序对 都合法(允许 ),故方案数为 。
合法性检查
- 若 ,直接输出 。
- 遍历 时,若出现 或 或 ,则无解。
算法步骤
- 读入 。
- 对每个测试用例:
- 读入 和数组 。
- 若 ,输出 并继续。
- 初始化 ,(表示 )。
- 对于 到 :
- 计算 ,若 ,标记非法。
- 计算 ,若 ,标记非法。
- 根据 更新 :
- :不变
- :
- :
- 更新 。
- 若未出现非法,输出 ,否则输出 。
复杂度
每个测试用例 ,所有测试用例的 之和不超过 ,因此总复杂度 ,非常高效。
代码实现(C++)
#include <bits/stdc++.h> using namespace std; const int MOD = 998244353; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int n; cin >> n; vector<int> a(n + 1); for (int i = 1; i <= n; ++i) cin >> a[i]; if (a[n] != n) { cout << "0\n"; continue; } long long ans = 1; int cur = 0; // a[i-1] bool ok = true; for (int i = 1; i <= n; ++i) { int d = a[i] - cur; if (d < 0 || d > 2) { ok = false; break; } int free = i - 1 - cur; if (free < 0) { ok = false; break; } if (d == 1) { ans = ans * (1 + 2LL * free) % MOD; } else if (d == 2) { ans = ans * (1LL * free * free % MOD) % MOD; } cur = a[i]; } cout << (ok ? ans : 0) << '\n'; } return 0; }验证样例
输入:
5 5 1 2 3 4 5 6 0 2 2 2 4 6 6 0 1 3 4 5 5 6 1 2 3 2 4 6 15 0 0 1 1 1 2 3 4 5 6 7 9 11 13 15输出:
1 4 0 0 532305727与题目示例完全一致。
-
- 1
信息
- ID
- 6442
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 9
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者