动态规划-数字三角形模型专题(题目汇总)
动态规划思想
与暴力搜索的区别
暴搜每次只能处理一种情况,因此效率低下;而动态规划的一个状态包含了若干种情况,是满足某个条件的情况集合,每次状态转移就能涉及若干情况,因此效率更高
数字三角形模型
AcWing 1015. 摘花生
题意
n * m的矩阵,起点位置在 (1,1),终点位置是 (n,m),接着给定该矩阵上,每个位置(x,y)上的物品的价值w。
现需要我们制定一个方案:
从起点出发,只能向右或向下走,如何走到终点,才能使经过的所有格子的物品总价值最大。
题解
状态表示
集合:定义f[i] [j]为从(1, 1)到达(i, j)的所有方案
属性:最大值
状态转移
- (i, j)从(i-1, j),即上方过来
- (1, 1) --->(i-1, j)->(i,j):f[i] [j] = f[i - 1] [j] + w[i] [j]
- --->:多条,且取最大,发现正好是dp[i-1] [j] 的含义
- (i, j)从(i, j-1),即左方过来
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, m;
int w[N][N];
int f[N][N];
int main()
{
int T;
scanf("%d", &T);
while (T -- )
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &w[i][j]);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
f[i][j] = max(f[i - 1][j], f[i][j - 1]) + w[i][j];
printf("%d\n", f[n][m]);
}
return 0;
}
AcWing 1018. 最低通行费
题意
n * n的矩阵,起点位置在 (1,1),终点位置是 (n,n),接着给定该矩阵上,每个位置(x,y)上的物品的价值w。
现需要我们制定一个方案:
从起点出发,允许上下左右走,但最多只能走(2*n - 1)步,如何走到终点,才能使经过的所有格子的物品总价值最小。
题解
2*n - 1=>不能走回头路
变为上题
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, INF = 1e9;
int n;
int w[N][N];
int f[N][N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
scanf("%d", &w[i][j]);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == 1 && j == 1) f[i][j] = w[i][j]; // 特判左上角
else
{
f[i][j] = INF;
if (i > 1) f[i][j] = min(f[i][j], f[i - 1][j] + w[i][j]); // 只有不在第一行的时候,才可以从上面过来
if (j > 1) f[i][j] = min(f[i][j], f[i][j - 1] + w[i][j]); // 只有不在第一列的时候,才可以从左边过来
}
printf("%d\n", f[n][n]);
return 0;
}
最小值简单写法
当然也可以把f[0][1]或f[1][0]置为0,使得f[1] [1] = min(f[0] [1], f[0] [1]) + a[1] [1] = 0 + a[1] [1] = a[1] [1]就行,这样有个好处,循环内部不用特判,代码简洁。
memset(f, 0x7f, sizeof f);
f[0][1] = 0; // 也可换成f[1][0] = 0
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
f[i][j] = min(f[i-1][j], f[i][j-1]) + a[i][j];
cout << f[n][n] << endl;
AcWing 1027. 方格取数
题意
n * n的矩阵,起点位置在 (1,1),终点位置是 (n,n),接着给定该矩阵上,每个位置(x,y)上的物品的价值w。
现需要我们制定一个方案:
从起点出发,允许向下和向右走,但要走两次,如何制定路线,才能使经过的所有格子的物品总价值最大。
题解
状态表示:
f[i1 j1, i2 j2]表示所有从(1,1),(1,1)分别走到(i1 j1), (i2 j2)的路径的最大值。
如何处理“同一 个格子不能被重复选择"
==只有在i1 + j1 i2 + j2 == k时,两条路径的格子才可能重合
f[k, i1, i2]表示所有从(1,1),(1,1)分别走到(i1,k-i1), (i2,k-i2)的路径的最大值。
另一个理解方式,k表示走的步数,只有从一个起点出发走相同的步数,点才可能重合。
状态转移
状态划分:
- 第一条:下, 第二条:下
- 第一条:(1,1)--->(i1-1, j1)->(i1,j1)
- 第一条:(1,1)--->(i2-1,j2)->(i2,j2)
- --->:多条,且取最大,(1,1)--->(i1-1, j1)和(1,1)--->(i2-1,j2) 可以用f(k - 1, i1 - 1, i2 - 1)表示
- 需要判断重合不重合
- 第一条:下, 第二条:上
- 第一条:上, 第二条:下
- 第一条:上, 第二条:上
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 15;
int n;
int w[N][N];
int f[N * 2][N][N];
int main()
{
scanf("%d", &n);
int a, b, c;
while (cin >> a >> b >> c, a || b || c) w[a][b] = c;
for (int k = 2; k <= n + n; k ++ )
for (int i1 = 1; i1 <= n; i1 ++ )
for (int i2 = 1; i2 <= n; i2 ++ )
{
int j1 = k - i1, 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 = f[k][i1][i2];
x = max(x, f[k - 1][i1 - 1][i2 - 1] + t);
x = max(x, f[k - 1][i1 - 1][i2] + t);
x = max(x, f[k - 1][i1][i2 - 1] + t);
x = max(x, f[k - 1][i1][i2] + t);
}
}
printf("%d\n", f[n + n][n][n]);
return 0;
}
AcWing 275. 传纸条
题意
n * m的矩阵,A从 (1,1)到(n,m),B从(n,m)到(1,1),接着给定该矩阵上,每个位置(x,y)上的物品的价值w。
现需要我们制定一个方案:
A允许向下和向右走,B允许向上和向左走,当两人的路线不能有交集,如何制定路线,才能使经过的所有格子的物品总价值最大。
题解
不论是在 方格取数 中,还是在本题中,最优解永远不会由两段相交的路径组成。
那么代码中的相关位置的判断在事实上是起到了上述的确定是让蓝色还是红色走虚线的效果。
首先考虑路径有交集该如何处理。
可以发现交集中的格子一定在每条路径的相同步数处。因此可以让两个人同时从起点出发,每次同时走一步,这样路径中相交的格子一定在同一步内。
状态表示:f[k, i, j] 表示两个人同时走了k步,第一个人在 (i, k - i) 处,第二个人在 (j, k - j)处的所有走法的最大分值。
状态计算:按照最后一步两个人的走法分成四种情况:
- 两个人同时向右走,最大分值是 f[k - 1, i, j] + score(k, i, j);
- 第一个人向右走,第二个人向下走,最大分值是 f[k - 1, i, j - 1] + score(k, i, j);
- 第一个人向下走,第二个人向右走,最大分值是 f[k - 1, i - 1, j] + score(k, i, j);
- 两个人同时向下走,最大分值是 f[k - 1, i - 1, j - 1] + score(k, i, j);
- 注意两个人不能走到相同格子,即i和j不能相等。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 55;
int n, m;
int g[N][N];
int f[N * 2][N][N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &g[i][j]);
for (int k = 2; k <= n + m; k ++ )
for (int i = max(1, k - m); i <= n && i < k; i ++ )
for (int j = max(1, k - m); j <= n && j < k; j ++ )
for (int a = 0; a <= 1; a ++ )
for (int b = 0; b <= 1; b ++ )
{
int t = g[i][k - i];
if (i != j || k == 2 || k == n + m) // 除了起点和终点之外,其余每个格子只能走一次
{
t += g[j][k - j];
f[k][i][j] = max(f[k][i][j], f[k - 1][i - a][j - b] + t);
}
}
printf("%d\n", f[n + m][n][n]);
return 0;
}