给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
自顶向下,末状态不唯一。由于结果与路径顺序无关,因此我们可以反过来,自底向上,这样末状态唯一,为数字三角形的顶点(1, 1)
。
状态表示:dp[i][j]
表示从底向上,走到(i, j)
的所有路径的最大值。
状态计算:(i, j)
可以由(i + 1, j)
和(i + 1, j + 1)
两个状态转移过来,即dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + w[i][j]
#include
#include
#include
using namespace std;
int main()
{
int n;
cin >> n;
vector<vector<int>> w(n + 1, vector<int>(n + 1, 0));
vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= i; j++) {
cin >> w[i][j];
}
}
// 初始化最底层
for(int i = 1; i <= n; i++) dp[n][i] = w[n][i];
// 自底向上
for(int i = n - 1; i >= 1; i--) {
for(int j = 1; j <= i; j++) {
dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + w[i][j];
}
}
cout << dp[1][1] << endl;
return 0;
}
时间复杂度为 O ( n 2 ) O(n^2) O(n2)
Hello Kitty想摘点花生送给她喜欢的米老鼠。
她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。
地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。
Hello Kitty只能向东或向南走,不能向西或向北走。
问Hello Kitty最多能够摘到多少颗花生。
状态表示:dp[i][j]
表示所有从起点(1, 1)
走到(i, j)
的路线中,摘取花生的最大值
状态计算:
(i, j)
这个状态要么从上面那个点(i-1, j)
转移过来,要么从左边那个点(i, j-1)
转移过来,二者取一个最大值dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + w[i][j]
#include
#include
#include
using namespace std;
int main()
{
int t;
cin >> t;
// t组数据
while( t-- ) {
int r, c;
cin >> r >> c;
vector<vector<int>> w(r + 1, vector<int>(c + 1, 0));
for(int i = 1; i <= r; i++) {
for(int j = 1; j <= c; j++) {
cin >> w[i][j];
}
}
vector<vector<int>> dp(r + 1, vector<int>(c + 1, 0));
for(int i = 1; i <= r; i++) {
for(int j = 1; j <= c; j++) {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + w[i][j];
}
}
cout << dp[r][c] << endl;
}
return 0;
}
时间复杂度为 O ( N 2 ) O(N^2) O(N2) 级别
一个商人穿过一个 N×N 的正方形的网格,去参加一个非常重要的商务活动。
他要从网格的左上角进,右下角出。
每穿越中间 1 个小方格,都要花费 1 个单位时间。
商人必须在 (2N−1) 个单位时间穿越出去。
而在经过中间的每个小方格时,都需要缴纳一定的费用。
这个商人期望在规定时间内用最少费用穿越出去。
请问至少需要多少费用?
注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。
根据题意分析,为了获取最小费用,不能走回头路
状态表示:dp[i][j]
表示所有从起点(1, 1)
走到(i, j)
的路线中,缴纳的最小费用
状态计算:
(i, j)
这个状态要么从上面那个方格(i-1, j)
转移过来,要么从左边那个方格(i, j-1)
转移过来,二者取一个最小值dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + w[i][j]
#include
#include
#include
using namespace std;
int main()
{
int n;
cin >> n;
vector<vector<int>> w(n + 1, vector<int>(n + 1, 0));
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
cin >> w[i][j];
}
}
vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
// 初始化边界
for(int i = 1; i <= n; i++) {
dp[i][1] = dp[i-1][1] + w[i][1];
dp[1][i] = dp[1][i-1] + w[1][i];
}
for(int i = 2; i<= n; i++) {
for(int j = 2; j <= n; j++) {
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + w[i][j];
}
}
cout << dp[n][n] << endl;
return 0;
}
时间复杂度为 O ( N 2 ) O(N^2) O(N2)
设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:
某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点。
在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。
这题与摘花生问题的不同之处在于,要走两次,且同一个格子的数只能被取一次,取完后归 0。
同一个格子的数只能被取一次的处理方式
i1 + j1 == i2 + j2
时,两条路径的格子才可能重合i1 + j1 == i2 + j2
时,分别处理格子重合和不重合的情况状态表示:
dp[k][i1][i2]
:从起点分别走到(i1, k - i1)
和(i2, k - i2)
的路径中,取得数字和的最大值。状态计算:
dp[k][i1][i2]
,两条路线可能的状态转移情况:
dp[k+1][i1+1][i2+1]
dp[k+1][i1+1][i2]
dp[k+1][i1][i2]
dp[k+1][i1][i2+1]
+ w[i1][j1]
+ w[i1][j1] + w[i2][j2]
#include
#include
using namespace std;
int main()
{
int n;
cin >> n;
vector<vector<int>> w(n + 1, vector<int>(n + 1, 0));
vector<vector<vector<int>>> dp(n * 2 + 1, vector<vector<int>>(n + 1, vector<int>(n + 1, 0)));
int a, b, c;
// 当a,b,c均为0时,停止输入
while( cin >> a >> b >> c, a || b || c ) {
w[a][b] = c;
}
for(int k = 2; k <= n*2; k++) {
for(int i1 = 1; i1 <= n; i1++) {
for(int i2 = 1; i2 <= n; i2++) {
int j1 = k - i1;
int j2 = k - i2;
// 判断是否越界
if(j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n) {
// 格子重合
int t = w[i1][j1];
// 格子不重合
if(i1 != i2) t += w[i2][j2];
int &x = dp[k][i1][i2]; // 引用,简化代码
x = max(x, dp[k-1][i1-1][i2-1] + t); // 下下
x = max(x, dp[k-1][i1-1][i2] + t); // 下右
x = max(x, dp[k-1][i1][i2-1] + t); // 右下
x = max(x, dp[k-1][i1][i2] + t); // 右右
}
}
}
}
cout << dp[n*2][n][n] << endl;
return 0;
}
时间复杂度为 O ( N 3 ) O(N^3) O(N3)