题目链接
题目描述
在一个 的网格迷宫中,每个方格
都有一定数量的金币。小K从左上角
出发,每回合只能向右或向下移动一格。
迷宫中存在动态障碍:已知有 条信息,每条信息形如
,表示方格
会在第
回合开始时永久变成墙。
小K的目标是规划一条路径,使得收集到的金币总数最多。一个方格中的金币只能被收集一次。
解题思路
这是一个带有时间限制的路径规划问题,是经典网格动态规划的变种。
1. 关键点:回合数与坐标的关系
小K从 出发,每回合移动一格。要想到达方格
,必须经过
次向下移动和
次向右移动,总共移动
次。
这意味着,小K 到达 方格 的时刻(即完成的移动回合数)恰好是
。例如,到达
需要1回合,即
;到达
需要2回合,即
。
一个方格 在第
回合会变成墙。因此,小K必须在第
回合之前到达该方格,即到达时间
必须小于
。
- 可通行条件:小K能进入方格
的充要条件是
,其中
是该方格变成墙的回合数(如果永不变成墙,则
为无穷大)。
2. 动态规划设计
-
状态定义:
表示从起点
出发,到达方格
时能收集到的最多金币数。
-
状态转移: 由于只能向右或向下移动,要到达
,只能从上方
或左方
转移而来。因此,状态转移方程为:
-
约束条件: 在计算
之前,我们必须首先检查方格
是否可通行。
- 如果
,则该方格无法到达,
应设为一个特殊值(如-1),表示不可达。
- 在进行状态转移时,如果来源方格(如
)的值为不可达,则不能从该方向转移。
- 如果
-
基本情况:
- 起点
的到达时间为
。题目保证信息中的
,且特别说明即使起点在第一回合变墙也不受影响。因此,起点
总是可以到达的。
。
- 起点
-
最终答案: 题目没有指定终点,小K的路径可以在任何一个可达的方格结束。因此,最终的答案是整个
表中的最大值。
3. 实现细节
- 预处理墙信息:为了方便查询,我们可以用一个二维数组
wall_time[N+1][M+1]
来存储每个方格变墙的时间,初始值设为一个很大的数。 - DP表初始化:可以将
dp
表初始化为一个负数,表示除起点外均不可达。 - 遍历顺序:从上到下、从左到右遍历网格,计算每个位置的
dp
值。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const long long INF = 1e18;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, m;
cin >> n >> m;
vector<vector<int>> coins(n + 1, vector<int>(m + 1));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> coins[i][j];
}
}
int k;
cin >> k;
vector<vector<int>> wall_time(n + 1, vector<int>(m + 1, 1e9));
for (int i = 0; i < k; ++i) {
int r, c, t;
cin >> r >> c >> t;
wall_time[r][c] = t;
}
vector<vector<long long>> dp(n + 1, vector<long long>(m + 1, -INF));
// Base case: (1, 1)
dp[1][1] = coins[1][1];
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (i == 1 && j == 1) continue;
// 检查当前格子是否可达
int arrival_time = i + j - 2;
if (arrival_time >= wall_time[i][j]) {
continue; // 此格子无法进入
}
long long from_up = -INF;
if (i > 1 && dp[i - 1][j] != -INF) {
from_up = dp[i - 1][j];
}
long long from_left = -INF;
if (j > 1 && dp[i][j - 1] != -INF) {
from_left = dp[i][j - 1];
}
long long prev_max = max(from_up, from_left);
if (prev_max != -INF) {
dp[i][j] = prev_max + coins[i][j];
}
}
}
long long max_coins = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
max_coins = max(max_coins, dp[i][j]);
}
}
cout << max_coins << endl;
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 n = sc.nextInt();
int m = sc.nextInt();
int[][] coins = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
coins[i][j] = sc.nextInt();
}
}
int k = sc.nextInt();
int[][] wallTime = new int[n + 1][m + 1];
for (int i = 0; i <= n; i++) {
Arrays.fill(wallTime[i], 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] = t;
}
long[][] dp = new long[n + 1][m + 1];
for (int i = 0; i <= n; i++) {
Arrays.fill(dp[i], Long.MIN_VALUE);
}
// Base case: (1, 1)
dp[1][1] = coins[1][1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 1 && j == 1) continue;
int arrivalTime = i + j - 2;
if (arrivalTime >= wallTime[i][j]) {
continue;
}
long fromUp = Long.MIN_VALUE;
if (i > 1 && dp[i - 1][j] != Long.MIN_VALUE) {
fromUp = dp[i - 1][j];
}
long fromLeft = Long.MIN_VALUE;
if (j > 1 && dp[i][j - 1] != Long.MIN_VALUE) {
fromLeft = dp[i][j - 1];
}
long prevMax = Math.max(fromUp, fromLeft);
if (prevMax != Long.MIN_VALUE) {
dp[i][j] = prevMax + coins[i][j];
}
}
}
long maxCoins = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
maxCoins = Math.max(maxCoins, dp[i][j]);
}
}
System.out.println(maxCoins);
}
}
import sys
def main():
try:
n, m = map(int, sys.stdin.readline().split())
coins = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
row = list(map(int, sys.stdin.readline().split()))
for j in range(1, m + 1):
coins[i][j] = row[j - 1]
k = int(sys.stdin.readline())
wall_time = [[float('inf')] * (m + 1) for _ in range(n + 1)]
for _ in range(k):
r, c, t = map(int, sys.stdin.readline().split())
wall_time[r][c] = t
except (IOError, ValueError):
return
dp = [[-1] * (m + 1) for _ in range(n + 1)]
# Base case: (1, 1)
dp[1][1] = coins[1][1]
for i in range(1, n + 1):
for j in range(1, m + 1):
if i == 1 and j == 1:
continue
arrival_time = i + j - 2
if arrival_time >= wall_time[i][j]:
continue
from_up = -1
if i > 1 and dp[i - 1][j] != -1:
from_up = dp[i - 1][j]
from_left = -1
if j > 1 and dp[i][j - 1] != -1:
from_left = dp[i][j - 1]
prev_max = max(from_up, from_left)
if prev_max != -1:
dp[i][j] = prev_max + coins[i][j]
max_coins = 0
for i in range(1, n + 1):
for j in range(1, m + 1):
max_coins = max(max_coins, dp[i][j])
print(max_coins)
if __name__ == "__main__":
main()
算法及复杂度
- 算法: 动态规划 (DP)
- 时间复杂度:
。我们需要遍历整个
的网格一次来填充DP表。预处理墙的信息需要
。因此总时间复杂度为
。
- 空间复杂度:
。主要开销是存储金币网格、墙时间网格以及DP表。