题目链接

旅行者的大逃脱

题目描述

在一个 的网格中,旅行者从左上角 出发,目标是到达右下角 。初始时刻为 。旅行者的移动规则如下:

  • 每次移动,只能选择向下或向右,并且可以一次前进任意正整数步
  • 每完成一次移动,时刻(即移动次数)加

个检查官。第 个检查官将在时刻 (即移动次数达到 时)出现在格子 并一直驻守。旅行者如果在第 次移动时,其路径(包括起点和终点)接触到 ,就会被捕获。大门在时刻 后关闭,若到达 时移动次数已经大于 ,也算失败。

你需要计算:

  1. 逃离所需的最短时间(最少移动次数)。
  2. 在所有合法方案中,可以逃脱的总方案数量(对 取模)。 如果无法逃脱,输出

解题思路

本题要求计算两个独立的值:一是能到达终点的最短时间(最少移动次数),二是所有能在时间限制 内到达终点的方案总数

我们可以使用动态规划来解决,并通过时间步(移动次数)进行迭代。由于当前时刻的状态只与上一时刻有关,我们可以使用滚动数组优化空间。

状态定义:在当前时刻 到达格子 的方案数。

初始化 (使用0-indexed坐标),表示在时刻 (移动 次),位于起点的方案数为

状态转移: 我们迭代时刻 。在每个时刻 ,我们根据 时刻的状态 计算出 时刻的状态 的值由两部分贡献:

  1. 从上方格子 (其中 ) 移动而来。
  2. 从左方格子 (其中 ) 移动而来。

转移优化 (前缀和): 朴素的转移需要遍历所有来源点,效率低下。我们可以使用前缀和来优化。

  • 计算来自上方的贡献:遍历每一列 ,用一个变量 维护 的方案数之和。在遍历 时, 直接加上 自身则累加
  • 计算来自左方的贡献:同理,遍历每一行 ,用变量 维护 的方案数之和,并更新

障碍处理: 在每个时刻 (对应第 次移动) 开始计算前:

  1. 将所有在时刻 或之前出现的障碍物位置记录在 数组中。
  2. 对于恰好在时刻 出现的障碍 ,执行 。这代表在 时刻到达 的所有方案在时刻 都无法从该点出发,因此这些方案数作废。
  3. 在计算前缀和时,若遇到任何已标记的障碍 (),则将前缀和 清零,表示此路不通,无法跨越障碍。

算法流程

  1. 初始化 。初始化
  2. 外层循环
  3. 在循环 内部: a. 处理在时刻 新出现的障碍,更新 数组,并执行
    b. 初始化下一时刻的状态数组 为全
    c. 按行转移:使用前缀和计算所有从左方来的贡献,累加到 中。
    d. 按列转移:使用前缀和计算所有从上方来的贡献,累加到 中。
    e. 将 的状态赋给 ,完成一次迭代。
    f. 统计结果
    - 如果 仍为 ,说明这是第一次到达终点,记录
    - 将 累加到 中。
  4. 循环结束后,根据 是否为 输出结果。

代码

#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状态的滚动数组和检查官信息。