牛客网. 龙与地下城游戏

题目概述

解题思路

这道题看上去要用动态规划处理。动归的一个关键在于构造最优子问题(也就是写递推公式)。对于这种矩阵形式的动态规划,另一个关键(也是我之前选择性忽略的)就是确定动态规划的方向(其实是在确立递推公式的初始的条件)。比方说,对于一个二维矩阵,我们究竟是从它的右下角往左上角递推,还是从它的左上角往右下角递推呢?对于很多题目来说,似乎两者都是OK的;但是在有些题目里(比如说这道题),可能选择就只有一种了。

图1. 动态规划求解的示意图

对这题来说,我们首先要确定递推公式表示的内容是什么:我们维护一个二维数组dp,数组的元素dp[i][j]表示从起始位置走到该处时,至少应具备多少的血量(也就是到这个地方的时候,最少应该有多少血)。那我们要求的值就是dp[0][0],也就是从起始位置出发的时候最少应该有多少血。我们的已知条件是:dp[M-1][N-1] = min(1, 1 - map[M-1][N-1])。这个已知条件是怎么来的呢?实际上,我们只需考虑两种情况:

  • 如果map[M-1][N-1]  < 0,也就是说到走到这个位置前,骑士血量至少是1-map[M-1][N-1];
  • 如果map[M-1][N-1] >= 0,那么骑士只要在map[M-1][N-2]或map[M-2][N-1]处达到最低血量的要求——1,走过来就没有问题了。

接下来,我们考虑如何构造递推公式。既然递推公式的初始值从右下角出发,而且dp[i][j]仅与它相邻的右/下两个元素相关,那么我们要做的工作还有两步:

  • 把dp[M-1][ J ]和dp[ I ][N-1]的值都算出来(0 <= J < N-1, 0 <= I < M-1),对应上图中紫色框的部分;
  • 构造关于dp[i][j]和dp[i][j+1]&dp[i+1][j]的递推式。

在做第二步的时候,我们可以先参考一下第一步的过程,看看能否抽象出它的步骤。求解的示意图如上图所示,我们从右下角绿勾位置出发,往左/上两个角度遍历求解。我们用题目给定的例子来推一下第一步的过程:

先考虑dp[0][2]~dp[2][2]和dp[2][0]~dp[2][2]的值该怎么取,下面给出求解过程。

map矩阵

-2   -3   3
-5  -10   1
 0   30  -5

dp 矩阵

 ?    ?   ?
 ?    ?   ?
 ?    ?   max(1, 1-(-5))

 ?    ?   ?
 ?    ?   max(max(1, 1-1), 6-1)
 ?    ?   6

 ?    ?   max(max(1, 1-3), 5-3)
 ?    ?   5
 ?    ?   6

 ?    ?   2
 ?    ?   5
 ?    max(1, 6-30)   6

 ?    ?   ?
 ?    ?   ?
 max(1, 1-0)     1   6

初始矩阵:
 ?    ?   2
 ?    ?   5
 1    1   6

可以发现在矩阵的最下/右列,递推公式是:

  • dp[i][N-1] = max(1, dp[i+1][N-1] - map[i][N-1])
  • dp[M-1][j] = max(1, dp[M-1][j+1] - map[M-1][j])

推而广之即可得到:

dp[i][j] = min( max(1, dp[i+1][j] - map[i][j]), \ max(1, dp[i][j+1] - map[i][j]))

由递推公式可知,这道题的空间复杂度是可以优化到O(min(M, N))的,因为它每次递推时,只需要用到上一层的信息。

方法性能

示例代码

#include<algorithm>
#include<iostream>
#include<math.h>
#include<queue>
#include<stdio.h>
#include<string.h>
#include<string>
#include<vector>
#define LEN 1000

using namespace std;

int A[LEN][LEN], dp[LEN][LEN], M, N;

int main()
{
	scanf("%d%d", &M, &N);
	for (int i = 0; i < M; ++i)
		for (int j = 0; j < N; ++j)
			scanf("%d", &A[i][j]);

	dp[M - 1][N - 1] = max(1, 1 - A[M - 1][N - 1]);
	for (int j = N - 2; j >= 0; --j)
		dp[M - 1][j] = max(1, dp[M - 1][j + 1] - A[M - 1][j]);

	for (int i = M - 2; i >= 0; --i)
	{
		dp[i][N - 1] = max(1, dp[i + 1][N - 1] - A[i][N - 1]);
		for (int j = N - 2; j >= 0; --j)
			dp[i][j] = min(max(1, dp[i + 1][j] - A[i][j]), max(1, dp[i][j + 1] - A[i][j]));
	}

	printf("%d", dp[0][0]);

	return 0;
}

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值