最近在补全提高课所有题目的题解,引荐一下汇总的地方提高课题解汇总
题目描述
给定一个 n×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分析法
{状态表示f(i,j){属性:从起点出发,走到第i行第j列的所有方案 集合:方案中的路线经过的所有物品的总价值最大Max 状态转移f(i,j)=max{f(i−1,j),f(i,j−1)}+w(i,j)
集合划分
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;
}
学会了记忆化搜索,感谢&&膜拜大佬。
#include <bits/stdc++.h> using namespace std; const int N =105; int rec[N][N],f[N][N],t,c,r; int dp(int x,int y){ if(x < 1 || y < 1 )return 0; if(f[x][y] > 0)return f[x][y]; return f[x][y] = max(dp(x-1,y),dp(x,y-1))+rec[x][y]; } int main(){ cin >> t; while(t--){ cin >> r >> c; memset(f,-1,sizeof(f)); memset(rec,0,sizeof(rec)); for(int i = 1; i <= r; ++i){ for(int j = 1; j <= c; ++j){ cin >> rec[i][j]; } } cout << dp(r,c) << endl; } return 0; }
大佬,请问下,为什么只能倒着搜呢,从(r, c)—>(1,1)
记忆化搜索运行时间甚至比不用更长..代码量多了那这还有必要吗?话说用了不应该时间短些的吗?
这个可能是数据量的问题吧,如果数据多时间复杂度的排序应该是DP(本题可以做的话) > 记忆化 > 暴力搜索。
记搜本来就慢啊,但是好理解
记忆化搜索本来就慢
请问是不是如果都从0开始枚举会数组越界啊?而且为什么这道题不用初始化f[1][1]呢?
状态转移中有 i−1,如果从 0 开始要特判
初始化看初始状态的要求
动态规划,初始递推值的设置是第一个环节,有时这个环节也可以省略,比如本题。我们来看下递推关系式:
f[i][j] = max(f[i - 1][j], f[i][j - 1]) + w[i][j];
当
i=1,j=1
时:f[1][1]=max(f[0][1],f[1][0])+w[1][1];
因为我们设计的数组是从1行1列开始的,0行0列被留空了,默认值是0,所以
f[0][1]=0,f[1][0]=0
,执行的结果就是f[1][1]=w[1][1]
,也就是捎带着完成了递推值的初始化,当然,我们也可以手动对起始值进行手动初始化,然后再通过循环完成后续的递推,也是一样的。不同的题目,可能递推值就无法通过循环完成初始化,这时就需要我们手动完成初始化操作。感谢