最近在补全提高课所有题目的题解,引荐一下汇总的地方提高课题解汇总
题目描述
给定一个 $n \times m$ 的矩阵,起点位置在 $(1,1)$,终点位置是 $(n, m)$
接着给定该矩阵上,每个位置$(x,y)$上的物品的价值$w$
现需要我们制定一个方案:
从起点出发,只能向右或向下走,如何走到终点,才能使经过的所有格子的物品总价值最大
题解
这题是一道标准的动态规划问题,模型是数字三角形
我们先来分析一下
如果要进行动态规划,则用来表示当前状态的参数有哪些?
- 当前走到第$i$行
- 当前走到第$j$列
于是乎,我们可以开二维数组f[i][j]
来存储当前在$i$行$j$列的状态
而f[i][j]
的值,就是表示从起点走到该点经过的所有格子的价值总和的最大值
则最终答案的状态就是f[n][m]
然后再考虑状态转移方程
因为限制了只能向右或向下走,因此当前状态f[i][j]
只能由f[i-1][j]
或f[i][j-1]
转移过来
闫氏DP分析法
$$ \begin{cases} 状态表示\mathrm{f(i,j)} \begin{cases} 属性: 从起点出发,走到第\mathrm{i}行第\mathrm{j}列的所有方案 \\\ 集合: 方案中的路线经过的所有物品的总价值最大 Max \end{cases} \\\ 状态转移 f(i,j) = max\{f(i - 1, j), f(i, j - 1)\} + w(i,j) \end{cases} $$
集合划分
Code(循环写法)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int T;
int n, m;
int w[N][N];
int f[N][N];
int main()
{
cin >> T;
while (T -- )
{
memset(w, 0, sizeof w);
memset(f, 0, sizeof f);
cin >> n >> m;
for (int i = 1; i <= n; ++ i)
{
for (int j = 1; j <= m; ++ j)
{
cin >> w[i][j];
}
}
//dp
for (int i = 1; i <= n; ++ i)
{
for (int j = 1; j <= m; ++ j)
{
f[i][j] = max(f[i][j - 1], f[i - 1][j]) + w[i][j];
}
}
cout << f[n][m] << endl;
}
return 0;
}
Code(记忆化搜索写法)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int T;
int n, m;
int w[N][N];
int f[N][N];
int dp(int x, int y)
{
if (f[x][y] >= 0) return f[x][y];
if (x < 1 || x > n || y < 1 || y > m) return f[x][y] = -INF;
if (x == 1 && y == 1) return f[x][y] = w[x][y];
int t = 0;
t = max(t, dp(x - 1, y) + w[x][y]);
t = max(t, dp(x, y - 1) + w[x][y]);
return f[x][y] = t;
}
int main()
{
cin >> T;
while (T -- )
{
memset(w, 0, sizeof w);
memset(f, -1, sizeof f);
cin >> n >> m;
for (int i = 1; i <= n; ++ i)
{
for (int j = 1; j <= m; ++ j)
{
cin >> w[i][j];
}
}
cout << dp(n, m) << endl;
}
return 0;
}
学会了记忆化搜索,感谢&&膜拜大佬。
大佬,请问下,为什么只能倒着搜呢,从(r, c)—>(1,1)
记忆化搜索运行时间甚至比不用更长..代码量多了那这还有必要吗?话说用了不应该时间短些的吗?
这个可能是数据量的问题吧,如果数据多时间复杂度的排序应该是DP(本题可以做的话) > 记忆化 > 暴力搜索。
记搜本来就慢啊,但是好理解
记忆化搜索本来就慢
请问是不是如果都从0开始枚举会数组越界啊?而且为什么这道题不用初始化f[1][1]呢?
状态转移中有 $i - 1$,如果从 $0$ 开始要特判
初始化看初始状态的要求
动态规划,初始递推值的设置是第一个环节,有时这个环节也可以省略,比如本题。我们来看下递推关系式:
当
i=1,j=1
时:因为我们设计的数组是从$1$行$1$列开始的,$0$行$0$列被留空了,默认值是$0$,所以
f[0][1]=0,f[1][0]=0
,执行的结果就是f[1][1]=w[1][1]
,也就是捎带着完成了递推值的初始化,当然,我们也可以手动对起始值进行手动初始化,然后再通过循环完成后续的递推,也是一样的。不同的题目,可能递推值就无法通过循环完成初始化,这时就需要我们手动完成初始化操作。感谢