题目链接

网格迷宫

题目描述

在一个 的网格迷宫中,每个方格 都有一定数量的金币。小K从左上角 出发,每回合只能向右或向下移动一格。有 条信息,每条信息表示方格 会在第 回合变成永久的墙。小K在第 回合移动时,会先等所有该回合的墙出现后,再进行移动决策。你需要计算小K最多能收集到多少金币。

输入:

  • 第一行输入
  • 接下来 行,每行 个整数,表示每个方格的金币数。
  • 接下来一行输入一个整数
  • 接下来 行,每行三个整数 ,表示一条信息。

输出:

  • 输出一个整数,代表小K最多能收集到的金币数量。

解题思路

这是一个在带有动态障碍的网格上求最大收益路径的问题。问题的关键在于正确计算小K到达每个格子的回合数,从而判断该格子是否可达。

  1. 时间与位置的关系:

    • 小K从 出发,只能向右或向下移动。要到达方格 ,他必须走 步向下和 步向右,总共需要走 步。
    • 每次移动消耗一个回合,因此,进入方格 的那次移动发生在第 t = (i-1) + (j-1) 回合。
  2. 判断格子是否可达:

    • 根据题目“每回合开始时,方格先发生变化,小K再进行移动”,当小K在第 回合准备移动时,所有标记为第 回合或更早的墙都已经出现了。
    • 因此,一个方格 是可通行的,当且仅当它的建墙时间 wall_time[i][j] 严格大于 小K到达它的回合数 (i-1) + (j-1)
    • 这个判断逻辑也自然地处理了起点的特殊情况:对于 ,到达回合为0。任何墙的建立时间 ,所以 wall_time[1][1] > 0 恒成立,起点永远可达。
  3. 动态规划:

    • 状态定义: 表示从起点 出发,到达方格 时能收集到的最大金币数。
    • 状态转移:
      • 状态转移方程为:
      • 这个转移只有在 可通行,并且来源格子 可达(其DP值有效)时才发生。
    • 遍历顺序: 我们可以从上到下、从左到右遍历整个网格,依次计算每个格子的DP值。

最终,所有可达格子 值中的最大值,就是小K能收集到的最多金币数。

代码

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const long long INVALID = -1;

int main() {
    int R, C;
    cin >> R >> C;

    vector<vector<int>> coins(R + 1, vector<int>(C + 1));
    for (int i = 1; i <= R; ++i) {
        for (int j = 1; j <= C; ++j) {
            cin >> coins[i][j];
        }
    }

    int K;
    cin >> K;
    vector<vector<int>> wall_time(R + 1, vector<int>(C + 1, 1e9 + 7));
    for (int i = 0; i < K; ++i) {
        int r, c, t;
        cin >> r >> c >> t;
        wall_time[r][c] = min(wall_time[r][c], t);
    }
    
    vector<vector<long long>> dp(R + 1, vector<long long>(C + 1, INVALID));
    
    for (int i = 1; i <= R; ++i) {
        for (int j = 1; j <= C; ++j) {
            int arrival_round = (i - 1) + (j - 1);
            if (wall_time[i][j] <= arrival_round) {
                continue; // 此格在到达时已是墙
            }

            if (i == 1 && j == 1) {
                dp[i][j] = coins[i][j];
                continue;
            }
            
            long long from_top = (i > 1) ? dp[i - 1][j] : INVALID;
            long long from_left = (j > 1) ? dp[i][j - 1] : INVALID;

            if (from_top == INVALID && from_left == INVALID) {
                continue; // 无法从任何有效路径到达此格
            }
            
            dp[i][j] = max(from_top, from_left) + coins[i][j];
        }
    }

    long long max_coins = 0;
    for (int i = 1; i <= R; ++i) {
        for (int j = 1; j <= C; ++j) {
            max_coins = max(max_coins, dp[i][j]);
        }
    }

    cout << max_coins << "\n";
    return 0;
}
import java.util.Scanner;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int R = sc.nextInt();
        int C = sc.nextInt();

        int[][] coins = new int[R + 1][C + 1];
        for (int i = 1; i <= R; i++) {
            for (int j = 1; j <= C; j++) {
                coins[i][j] = sc.nextInt();
            }
        }

        int K = sc.nextInt();
        int[][] wallTime = new int[R + 1][C + 1];
        for (int[] row : wallTime) {
            Arrays.fill(row, Integer.MAX_VALUE);
        }
        for (int i = 0; i < K; i++) {
            int r = sc.nextInt();
            int c = sc.nextInt();
            int t = sc.nextInt();
            wallTime[r][c] = Math.min(wallTime[r][c], t);
        }

        long[][] dp = new long[R + 1][C + 1];
        for (long[] row : dp) {
            Arrays.fill(row, -1);
        }
        
        for (int i = 1; i <= R; i++) {
            for (int j = 1; j <= C; j++) {
                int arrivalRound = (i - 1) + (j - 1);
                if (wallTime[i][j] <= arrivalRound) {
                    continue; // 此格在到达时已是墙
                }

                if (i == 1 && j == 1) {
                    dp[i][j] = coins[i][j];
                    continue;
                }

                long fromTop = (i > 1) ? dp[i - 1][j] : -1;
                long fromLeft = (j > 1) ? dp[i][j - 1] : -1;

                if (fromTop == -1 && fromLeft == -1) {
                    continue; // 无法从任何有效路径到达此格
                }
                
                dp[i][j] = Math.max(fromTop, fromLeft) + coins[i][j];
            }
        }

        long maxCoins = 0;
        for (int i = 1; i <= R; i++) {
            for (int j = 1; j <= C; j++) {
                maxCoins = Math.max(maxCoins, dp[i][j]);
            }
        }
        System.out.println(maxCoins);
    }
}
# 读取输入
R, C = map(int, input().split())
coins = []
for _ in range(R):
    coins.append(list(map(int, input().split())))

K = int(input())
wall_time = {}
for _ in range(K):
    r, c, t = map(int, input().split())
    # 记录每个格子最早变成墙的时间
    if (r, c) not in wall_time or t < wall_time[(r, c)]:
        wall_time[(r, c)] = t

# dp[i][j] 表示到达 (i, j) 的最大金币数 (0-indexed)
dp = [[-1] * C for _ in range(R)]

for i in range(R):
    for j in range(C):
        arrival_round = i + j
        # 如果当前格子在到达时已是墙,则不可达
        if wall_time.get((i + 1, j + 1), float('inf')) <= arrival_round:
            continue
        
        if i == 0 and j == 0:
            dp[i][j] = coins[i][j]
            continue

        from_top = dp[i - 1][j] if i > 0 else -1
        from_left = dp[i][j - 1] if j > 0 else -1
        
        if from_top == -1 and from_left == -1:
            continue
            
        dp[i][j] = max(from_top, from_left) + coins[i][j]


max_coins = 0
for i in range(R):
    for j in range(C):
        max_coins = max(max_coins, dp[i][j])

print(max_coins)

算法及复杂度

  • 算法:动态规划
  • 时间复杂度: - 我们需要遍历整个网格一次来计算所有格子的DP值。
  • 空间复杂度: - 主要开销来自存储金币、墙体信息和DP状态的二维数组。