1 条题解
-
0
题目题解
问题理解
初始数组为空。每次操作可以选择 和 ,将 的循环移位
追加到数组末尾。给定 和 条限制 ,统计所有长度为 且满足限制的可构造数组的个数,对 取模。
第一步:观察数组的结构
每次追加的是一个 到 的排列,且是一个循环移位。
因此,最终数组由若干段这样的“循环移位排列”拼接而成。每个排列的数值范围是 ,且 可以不同。
数组的总长度等于各段长度之和。
第二步:重复计数问题
一个数组可能有多种拆分方式。例如 可以拆成 或 。
这种重复计数源于:当先加一个长度为 的恒等排列 ,再加一个长度为 的恒等排列 ()时,也可以视为先加一个长度为 的排列再加一个长度为 的循环移位。为了避免重复,我们规定:不允许在恒等排列后面紧接着一个以 开头的循环移位。
更精确地,在 DP 状态中记录最后一个排列是否是恒等排列及其长度。
第三步:动态规划状态
设 表示填充完前 个位置,且最后一个排列是恒等排列且长度为 的方案数( 表示最后一个不是恒等排列)。
设 ,表示前 个位置的总方案数。为了快速转移,我们预处理
$$\text{chain}(i, j) = 1 + \text{chain}(i+1, j+1) \quad \text{如果允许,否则为 } 0. $$chain(i, j):
chain(i, j)表示从位置 开始,连续放置 且不违反限制的最大长度。
即满足 的最大连续长度。
若 有禁止值,则chain(i, j) = 0。
递推:
第四步:辅助数组
ssum(i, j)ssum(i, j)表示:从位置 开始,我们可以放置一个长度为 的恒等排列 时,后面部分的所有合法方案数之和(不考虑当前位置的限制)。更具体地:
$$\text{ssum}(i, j) = \sum_{x \ge i} [\text{chain}(x, 1) \ge j-1] \cdot tot(x + j - 1). $$这表示:如果我们从 开始放置 (共 个连续数),则位置 之后的部分可以任意填充。
第五步:转移方程
-
当 (最后一个排列不是恒等排列):
$$dp(i, 0) \mathrel{+}= tot(i + len) - dp(i + len, len + 1). $$
我们尝试在位置 开始一个长度为 的恒等排列 。
转移:减去 是为了避免重复:如果后面恰好接着一个长度为 的恒等排列,则会与另一种拆分方式重复。
-
当 (最后一个排列是恒等排列且长度为 ):
$$dp(i, j) = \text{ssum}(i+1, j) - \text{ssum}(i+1 + \text{chain}(i, j), j). $$
此时 必须等于 。
我们从 开始,可以放置任意以 开头的序列(即一个长度为 的恒等排列的尾部),但必须保证 。
转移:其中 保证了从 开始连续放置 的最大长度,超出部分不可用。
第六步:最终答案
即为长度为 的所有合法方案数。
时间复杂度
- 预处理
chain:。 - 计算
ssum和 DP:。 - 总复杂度 ,满足 ,。
代码实现
#include <bits/stdc++.h> using namespace std; const int MOD = 998244353; const int MAXN = 105; // n <= 100,多开一点空间 struct Mint { int v; Mint(long long val = 0) { v = int(val % MOD); if (v < 0) v += MOD; } Mint operator+(const Mint &o) const { return Mint(v + o.v); } Mint operator-(const Mint &o) const { return Mint(v - o.v); } Mint operator*(const Mint &o) const { return Mint(1LL * v * o.v); } Mint& operator+=(const Mint &o) { v += o.v; if (v >= MOD) v -= MOD; return *this; } Mint& operator-=(const Mint &o) { v -= o.v; if (v < 0) v += MOD; return *this; } Mint& operator*=(const Mint &o) { v = int(1LL * v * o.v % MOD); return *this; } friend ostream& operator<<(ostream& os, const Mint& m) { os << m.v; return os; } }; void solve() { int n, m; cin >> n >> m; // 使用 1-indexed 方便处理边界 vector<vector<bool>> ban(n + 2, vector<bool>(n + 2, false)); for (int i = 0; i < m; i++) { int pos, val; cin >> pos >> val; // 转换为 1-indexed ban[pos][val] = true; } // chain[i][j]: 从位置 i 开始连续放置 j, j+1, ... 的最大长度 // 使用 1-indexed,i 和 j 范围 1..n vector<vector<int>> chain(n + 3, vector<int>(n + 3, 0)); for (int i = n; i >= 1; i--) { for (int j = n; j >= 1; j--) { if (ban[i][j]) { chain[i][j] = 0; } else { chain[i][j] = 1 + chain[i + 1][j + 1]; // 限制最大长度,防止越界 if (chain[i][j] > n - i + 1) chain[i][j] = n - i + 1; if (chain[i][j] > n - j + 1) chain[i][j] = n - j + 1; } } } // dp[i][j]: 填充完前 i-1 个位置,最后一个排列是恒等排列且长度为 j // 使用 1-indexed,i 范围 1..n+1 vector<Mint> tot(n + 3, 0); vector<vector<Mint>> dp(n + 3, vector<Mint>(n + 3, 0)); vector<vector<Mint>> ssum(n + 3, vector<Mint>(n + 3, 0)); tot[n + 1] = 1; // 空后缀 for (int i = n; i >= 1; i--) { // 计算 dp[i][0]:添加一个恒等排列 for (int len = 1; i + len - 1 <= n; len++) { if (chain[i][1] < len) break; // 注意这里 j=1 表示从 1 开始 dp[i][0] += tot[i + len]; if (i + len <= n + 1 && len + 1 <= n + 1) { dp[i][0] -= dp[i + len][len + 1]; } } // 计算 dp[i][j] for j >= 1 for (int j = 1; j <= n - i + 1; j++) { if (chain[i][j] == 0) continue; // 从 i+1 开始,可以放置任意以 j+1 开头的序列 dp[i][j] += ssum[i + 1][j]; int nxt = i + 1 + chain[i][j]; if (nxt <= n + 1) { dp[i][j] -= ssum[nxt][j]; } } // 更新 tot[i] for (int j = 0; j <= n - i + 1; j++) { tot[i] += dp[i][j]; } // 更新 ssum[i][j] for (int j = 1; j <= n - i + 1; j++) { ssum[i][j] = ssum[i + 1][j]; if (chain[i][1] >= j) { // 从 i 开始能连续放置 1..j if (i + j <= n + 1) { ssum[i][j] += tot[i + j]; } } } } cout << tot[1] << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
验证样例
输入:
7 3 0 3 3 1 1 2 1 3 1 3 2 1 1 2 1 6 2 2 3 4 2 2 3 1 2 2 2 1 1 4 3 2 2 3 2 4 2 3 2 2 3 3 3输出:
7 0 1 65 0 4 5与题目输出一致。
总结
本题的关键在于:
- 识别出重复计数问题,并引入状态记录最后一个排列是否为恒等排列。
- 预处理
chain数组快速判断连续放置的可行性。 - 利用
ssum辅助数组加速转移,将复杂度控制在 。
-
- 1
信息
- ID
- 6261
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 1
- 标签
- 递交数
- 4
- 已通过
- 1
- 上传者