题目链接
题目描述
在一个 的网格中,旅行者从左上角
出发,目标是到达右下角
。初始时刻为
。旅行者的移动规则如下:
- 每次移动,只能选择向下或向右,并且可以一次前进任意正整数步。
- 每完成一次移动,时刻(即移动次数)加
。
有 个检查官。第
个检查官将在时刻
(即移动次数达到
时)出现在格子
并一直驻守。旅行者如果在第
次移动时,其路径(包括起点和终点)接触到
且
,就会被捕获。大门在时刻
后关闭,若到达
时移动次数已经大于
,也算失败。
你需要计算:
- 逃离所需的最短时间(最少移动次数)。
- 在所有合法方案中,可以逃脱的总方案数量(对
取模)。 如果无法逃脱,输出
。
解题思路
本题要求计算两个独立的值:一是能到达终点的最短时间(最少移动次数),二是所有能在时间限制 内到达终点的方案总数。
我们可以使用动态规划来解决,并通过时间步(移动次数)进行迭代。由于当前时刻的状态只与上一时刻有关,我们可以使用滚动数组优化空间。
状态定义:
:在当前时刻
到达格子
的方案数。
初始化:
(使用0-indexed坐标),表示在时刻
(移动
次),位于起点的方案数为
。
状态转移:
我们迭代时刻 从
到
。在每个时刻
,我们根据
时刻的状态
计算出
时刻的状态
。
的值由两部分贡献:
- 从上方格子
(其中
) 移动而来。
- 从左方格子
(其中
) 移动而来。
转移优化 (前缀和): 朴素的转移需要遍历所有来源点,效率低下。我们可以使用前缀和来优化。
- 计算来自上方的贡献:遍历每一列
,用一个变量
维护
的方案数之和。在遍历
时,
直接加上
。
自身则累加
。
- 计算来自左方的贡献:同理,遍历每一行
,用变量
维护
的方案数之和,并更新
。
障碍处理:
在每个时刻 (对应第
次移动) 开始计算前:
- 将所有在时刻
或之前出现的障碍物位置记录在
数组中。
- 对于恰好在时刻
出现的障碍
,执行
。这代表在
时刻到达
的所有方案在时刻
都无法从该点出发,因此这些方案数作废。
- 在计算前缀和时,若遇到任何已标记的障碍 (
),则将前缀和
清零,表示此路不通,无法跨越障碍。
算法流程:
- 初始化
。初始化
,
。
- 外层循环
从
到
。
- 在循环
内部: a. 处理在时刻
新出现的障碍,更新
数组,并执行
。
b. 初始化下一时刻的状态数组为全
。
c. 按行转移:使用前缀和计算所有从左方来的贡献,累加到中。
d. 按列转移:使用前缀和计算所有从上方来的贡献,累加到中。
e. 将的状态赋给
,完成一次迭代。
f. 统计结果:
- 如果仍为
且
,说明这是第一次到达终点,记录
。
- 将累加到
中。
- 循环结束后,根据
是否为
输出结果。
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MOD = 998244353;
void solve() {
int n, m, q, k;
cin >> n >> m >> q >> k;
vector<vector<pair<int, int>>> listener(k);
for (int i = 0; i < q; ++i) {
int r, c, t;
cin >> r >> c >> t;
if (t - 1 < k) {
listener[t - 1].push_back({r - 1, c - 1});
}
}
vector<vector<long long>> f(n, vector<long long>(m, 0));
f[0][0] = 1;
long long total_ways = 0;
int shortest_time = -1;
vector<vector<int>> vis(n, vector<int>(m, 0));
for (int t = 0; t < k; ++t) {
for (auto const& [r, c] : listener[t]) {
vis[r][c] = 1;
f[r][c] = 0; // 关键:在t时刻出现在(r,c)的障碍,会使t-1时刻到达(r,c)的方案作废
}
vector<vector<long long>> g(n, vector<long long>(m, 0));
// 按行转移 (从左方来)
for (int i = 0; i < n; ++i) {
long long pre = 0;
for (int j = 0; j < m; ++j) {
if (vis[i][j]) {
pre = 0;
}
g[i][j] = (g[i][j] + pre) % MOD;
pre = (pre + f[i][j]) % MOD;
}
}
// 按列转移 (从上方来)
for (int j = 0; j < m; ++j) {
long long pre = 0;
for (int i = 0; i < n; ++i) {
if (vis[i][j]) {
pre = 0;
}
g[i][j] = (g[i][j] + pre) % MOD;
pre = (pre + f[i][j]) % MOD;
}
}
f = g;
if (f[n - 1][m - 1] > 0 && shortest_time == -1) {
shortest_time = t + 1;
}
total_ways = (total_ways + f[n - 1][m - 1]) % MOD;
}
if (shortest_time == -1) {
cout << -1 << "\n";
} else {
cout << total_ways << " " << shortest_time << "\n";
}
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
public class Main {
static final int MOD = 998244353;
public static void solve(Scanner sc) {
int n = sc.nextInt();
int m = sc.nextInt();
int q = sc.nextInt();
int k = sc.nextInt();
List<List<int[]>> listener = new ArrayList<>(k);
for (int i = 0; i < k; i++) {
listener.add(new ArrayList<>());
}
for (int i = 0; i < q; i++) {
int r = sc.nextInt();
int c = sc.nextInt();
int t = sc.nextInt();
if (t - 1 < k) {
listener.get(t - 1).add(new int[]{r - 1, c - 1});
}
}
long[][] f = new long[n][m];
f[0][0] = 1;
long totalWays = 0;
int shortestTime = -1;
int[][] vis = new int[n][m];
for (int t = 0; t < k; t++) {
for (int[] pos : listener.get(t)) {
vis[pos[0]][pos[1]] = 1;
f[pos[0]][pos[1]] = 0; // 关键:作废方案
}
long[][] g = new long[n][m];
// 按行转移 (从左方来)
for (int i = 0; i < n; i++) {
long pre = 0;
for (int j = 0; j < m; j++) {
if (vis[i][j] == 1) {
pre = 0;
}
g[i][j] = (g[i][j] + pre) % MOD;
pre = (pre + f[i][j]) % MOD;
}
}
// 按列转移 (从上方来)
for (int j = 0; j < m; j++) {
long pre = 0;
for (int i = 0; i < n; i++) {
if (vis[i][j] == 1) {
pre = 0;
}
g[i][j] = (g[i][j] + pre) % MOD;
pre = (pre + f[i][j]) % MOD;
}
}
f = g;
if (f[n - 1][m - 1] > 0 && shortestTime == -1) {
shortestTime = t + 1;
}
totalWays = (totalWays + f[n - 1][m - 1]) % MOD;
}
if (shortestTime == -1) {
System.out.println(-1);
} else {
System.out.println(totalWays + " " + shortestTime);
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
while (t-- > 0) {
solve(sc);
}
}
}
MOD = 998244353
def solve():
n, m, q, k = map(int, input().split())
listener = [[] for _ in range(k)]
for _ in range(q):
r, c, t = map(int, input().split())
if t - 1 < k:
listener[t - 1].append((r - 1, c - 1))
f = [[0] * m for _ in range(n)]
f[0][0] = 1
total_ways = 0
shortest_time = -1
vis = [[0] * m for _ in range(n)]
for t in range(k):
# 标记当前时刻生效的障碍,并作废相关方案
for r, c in listener[t]:
vis[r][c] = 1
f[r][c] = 0 # 关键点
g = [[0] * m for _ in range(n)]
# 按行转移 (从左方来)
for i in range(n):
pre = 0
for j in range(m):
if vis[i][j]:
pre = 0
g[i][j] = (g[i][j] + pre) % MOD
pre = (pre + f[i][j]) % MOD
# 按列转移 (从上方来)
for j in range(m):
pre = 0
for i in range(n):
if vis[i][j]:
pre = 0
g[i][j] = (g[i][j] + pre) % MOD
pre = (pre + f[i][j]) % MOD
f = g
# 统计结果
if f[n - 1][m - 1] > 0 and shortest_time == -1:
shortest_time = t + 1
total_ways = (total_ways + f[n - 1][m - 1]) % MOD
if shortest_time == -1:
print(-1)
else:
print(f"{total_ways} {shortest_time}")
def main():
T_cases = int(input())
for _ in range(T_cases):
solve()
if __name__ == "__main__":
main()
算法及复杂度
- 算法:动态规划(滚动数组 + 前缀和优化)
- 时间复杂度:
- 迭代
次,每次迭代对
网格进行线性扫描。
- 空间复杂度:
- 主要开销来自存储DP状态的滚动数组和检查官信息。