1 条题解
-
0
题目题解
问题理解
给定一个 的部分填充网格,每个单元格的值在 到 之间( 表示空)。要求计算填充所有空单元格的方式数,使得每一个子网格中都存在一条贪婪路径,该路径能达到该子网格内所有向下/向右路径中的最大值。
单网格的条件分析
设第 列的顶部值为 ,底部值为 。定义
并设
引理:一条从第 列顶部出发,在第 列从顶部切换到底部的路径的值为
证明:
$$\sum_{j=1}^i a_j + \sum_{j=i}^n b_j = a_1 + \sum_{j=2}^i a_j + b_i + \sum_{j=i+1}^n b_j. $$
路径经过 (顶部的前 列),然后经过 (底部的剩余列)。其和为而 ,因此
$$\text{路径值} = s + \sum_{j=2}^i a_j - \sum_{j=1}^{i-1} b_j = s + \sum_{j=1}^{i-1} (a_{j+1} - b_j) = s + \sum_{j=1}^{i-1} c_j. $$
贪婪路径的选择
设 为最长的前缀长度,使得所有 。
则最优的贪婪路径是在第 列切换行(若 则永远不切换)。条件:该前缀的和是所有前缀和中的最大值。这等价于要求:以 为左端点的任意子数组的和 。即
$$\forall t \ge p+1, \quad \sum_{j=p+1}^t c_j \le 0. $$
扩展到所有子网格
对于原网格的任意子网格,上述条件必须成立。由于子网格的选取包括所有连续的列区间,这等价于:对于原序列 (注意 的定义依赖于相邻两列),任意子数组的和必须满足:若该子数组的起点是某个“负转折点”,则其和 。
更精确地说,设 ,则条件转化为:对于任意 ,若 (即 ),则
即从每个负数位置开始的后缀和必须非负。
动态规划设计
我们按列从左到右处理,维护当前累积的“未重置”和。设 表示处理完前 列(即 )后,当前累积和为 的方案数,其中 表示从上一个负数位置开始的累积和(若未遇到负数则为正无穷大,但我们只关心非正的部分)。
实际上,我们只需要记录当前累积和 的范围。由于每个 满足 (因为 ),所以 的范围是 。
状态定义:
表示考虑前 个 值(即前 列),当前累积和(从上一个负数之后开始)为 的方案数。转移:
对于下一个 (取值范围 ,因为 ),分两种情况:-
若当前累积和 :
- 如果 ,则新累积和为 (若 则重置为 ?实际上条件要求从负数开始的累积和必须非正,因此 必须 ,否则该状态非法)
- 如果 ,则重置累积和为 (因为新的负数开始)
-
若当前累积和 (即尚未遇到负数),则:
- 如果 ,累积和增加,仍为正
- 如果 ,重置为
但为了满足所有子网格的条件,我们需要确保:一旦累积和变为负数,后续所有部分和必须保持非正。这等价于要求累积和始终 。
因此,我们可以将所有 的状态合并为一个特殊状态(表示尚未遇到负数),或者直接记录 但限制其不超过 。
简化模型
设 表示处理到第 个 ,当前从上一个负数开始的累积和为 ()的方案数。初始 (空序列,累积和为 )。
对于每个 ,枚举其所有可能取值 ,并考虑对 的影响:
- 若 :新累积和为 。必须满足 ,否则非法。
- 若 :重置累积和为 (因为新的负数开始)。
转移方程为:
$$g[i+1][j'] \leftarrow g[i][j] \cdot \text{ways}(c_{i+1} = j' - j \text{ 或 } j' = t) $$其中 表示有多少种 的组合使得 ,且满足给定网格中已填的值。
计算
对于固定的 ,。已知 ,,且网格中可能已有部分值。
对于每个 ,满足 且 的对数为:- 若 :( 从 到 ,)
- 若 :( 从 到 ,)
若已有固定值,则需检查一致性,并减少计数。
最终算法
- 读入网格,提取已知的 和 。
- 对于每个 ,计算 的可能取值及对应的方案数 。
- 初始化 。
- 对于 到 :
- 对于每个当前累积和 :
- 若 则跳过
- 对于每个可能的 ():
- 若 且 :$dp[i+1][j+t] \leftarrow dp[i+1][j+t] + dp[i][j] \cdot \text{cnt}[i+1][t]$
- 若 :$dp[i+1][t] \leftarrow dp[i+1][t] + dp[i][j] \cdot \text{cnt}[i+1][t]$
- 对于每个当前累积和 :
- 答案 (因为处理完所有 后,任何合法状态均可)。
时间复杂度
状态数 ,每个状态转移枚举 种 ,总复杂度 。
由于 ,,在 4 秒内可接受。
代码实现
#include <bits/stdc++.h> using namespace std; const int MOD = 998244353; void solve() { int n, k; cin >> n >> k; vector<int> a(n), b(n); for (int i = 0; i < 2; i++) { for (int j = 0; j < n; j++) { if (i == 0) cin >> a[j]; else cin >> b[j]; } } // 预处理 cnt[i][t] 表示第 i 个 c 值为 t 的方案数 vector<vector<int>> cnt(n - 1, vector<int>(2 * k, 0)); auto idx = [&](int t) { return t + k; }; // 偏移,使下标非负 for (int i = 0; i < n - 1; i++) { // 枚举 a[i+1] 和 b[i] 的可能值 for (int av = 1; av <= k; av++) { if (a[i+1] != -1 && a[i+1] != av) continue; for (int bv = 1; bv <= k; bv++) { if (b[i] != -1 && b[i] != bv) continue; int t = av - bv; cnt[i][idx(t)]++; } } } // DP vector<vector<int>> dp(n, vector<int>(2 * k, 0)); dp[0][idx(0)] = 1; // 初始累积和为 0 for (int i = 0; i < n - 1; i++) { vector<vector<int>> ndp(n, vector<int>(2 * k, 0)); for (int j = -k; j <= 0; j++) { int cur = dp[i][idx(j)]; if (cur == 0) continue; for (int t = -(k-1); t <= k-1; t++) { int ways = cnt[i][idx(t)]; if (ways == 0) continue; if (t >= 0) { if (j + t <= 0) { ndp[i+1][idx(j + t)] = (ndp[i+1][idx(j + t)] + 1LL * cur * ways) % MOD; } } else { ndp[i+1][idx(t)] = (ndp[i+1][idx(t)] + 1LL * cur * ways) % MOD; } } } dp = ndp; } int ans = 0; for (int j = -k; j <= 0; j++) { ans = (ans + dp[n-1][idx(j)]) % MOD; } cout << ans << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
总结
本题的核心是将网格条件转化为对序列 的约束,要求从每个负数位置开始的后续部分和非正。通过动态规划维护当前累积和,并利用预处理的 取值计数,可以在 时间内求解。
-
- 1
信息
- ID
- 6225
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 1
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者