线性 DP - 数字三角形模型
作者:互联网
原题 : 数字三角形
https://www.acwing.com/problem/content/900/
题意
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
分析
对于每一个位置 (x, y), 只有两种可能,向右下走或者向下走
状态表示: f[x,y]
表示所有从 (x, y) 到 (n,n) 的路径,指标是路径数字和
状态计算: f[x,y] = max(f[x+1, y], f[x+1, y+1]) + nums[x,y]
(x, y) 处状态的计算依赖于 (x+1, y) 和 (x+1, y+1) 处的计算,由此按照子问题图的拓扑序构建循环。
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
int nums[N][N];
int f[N][N];
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= i; j ++){
cin >> nums[i][j];
}
}
for(int x = n; x >= 1; x --)
for(int y = 1; y <= x; y ++){
f[x][y] = max(f[x+1][y], f[x+1][y+1]) + nums[x][y];
}
cout << f[1][1] << endl;
return 0;
}
衍生题1 : 摘花生
https://www.acwing.com/problem/content/1017/
题意
Hello Kitty想摘点花生送给她喜欢的米老鼠。
她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。
地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。
Hello Kitty只能向东或向南走,不能向西或向北走。
问Hello Kitty最多能够摘到多少颗花生。
分析
状态表示,f[i,j]
表示 (1,1) 到 (i, j) 的所有路径,指标是经过路径上的花生数最大值。
状态计算:只能从左边或者上面走到当前位置
f[i,j] = max(f[i-1, j], f[i, j-1]) + w[i,j]
代码
#include <iostream>
using namespace std;
const int N = 110;
int f[N][N], w[N][N];
int main(){
int r, c, t;
cin >> t;
while(t --){
cin >> r >> c;
for(int i = 1; i <= r; i ++){
for(int j = 1; j <= c; j ++){
cin >> w[i][j];
}
}
f[1][1] = w[1][1];
for(int i = 2; i <= r; i ++){
f[i][1] = f[i-1][1] + w[i][1];
}
for(int i = 2; i <= c; i ++){
f[1][i] = f[1][i-1] + w[1][i];
}
for(int i = 2; i <= r; i ++)
for(int j = 2; j <= c; j ++){
f[i][j] = max(f[i-1][j] , f[i][j-1]) + w[i][j];
}
cout << f[r][c] << endl;
}
return 0;
}
衍生题 2 : 最低通行费
https://www.acwing.com/problem/content/1020/
题意
一个商人穿过一个 N×N
的正方形的网格,去参加一个非常重要的商务活动。
他要从网格的左上角进,右下角出。
每穿越中间 1 个小方格,都要花费 1个单位时间。
商人必须在 (2N−1)个单位时间穿越出去。
而在经过中间的每个小方格时,都需要缴纳一定的费用。
这个商人期望在规定时间内用最少费用穿越出去。
请问至少需要多少费用?
注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。
分析
这个题乍一看跟数字三角形好像有点不太一样,因为题目没有限制只能向下向右,但是分析限制条件最多走 2 N - 1 步,如果只向下向右,最少也要走 2 N - 1 步,因此只能向下向右走。
状态表示: f[i,j]
从 (1,1) 到 (i, j) 的所有路径,指标是费用和的最小值
状态计算:f[i,j] = min(f[i-1,j], f[i, j-1]) + w[i,j]
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N][N], w[N][N];
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
cin >> w[i][j];
f[1][1] = w[1][1];
for(int i = 2; i <= n; i ++){
f[i][1] = f[i-1][1] + w[i][1];
f[1][i] = f[1][i-1] + w[1][i];
}
for(int i = 2; i <= n; i ++)
for(int j = 2; j <= n; j ++){
f[i][j] = min(f[i-1][j], f[i][j-1]) + w[i][j];
}
cout << f[n][n] << endl;
return 0;
}
衍生题3 : 方格取数
https://www.acwing.com/problem/content/description/1029/
题意
一个二维的矩阵,要寻找两条从 (1, 1) 到 (n, n) 的路径,使得两条路径数字和最大,一条路径取过一个位置之后这个位置的数字就变成 0
分析
状态表示f[k, i1, i2]
表示从 (1, 1) 到 (i1 ,k-i1) 和 (i2, k-i2) 的路径,指标是路径数字和最大值。
状态计算,每个路径上一步有两种走法,组合起来有四种走法
f[k,i1, i2] = max(f[k-1, i1-1, i2], f[k-1, i1, i2-1], f[k-1, i1-1, i2-1], f[k-1, i1, i2]) + t
t = w[i1][k-i1] + w[i2][k-i2] if i1 != i2, t = w[i1][k-i1] if i1 == i2
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 22;
int f[N][N][N], w[N][N];
int main(){
int n;
cin >> 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 < k; i1 ++)
for(int i2 = 1; i2 < k; i2++){
int j1 = k - i1, j2 = k - i2;
if(j1 <= n && j1 >=1 && j2 <= n && j2 >=1){
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] + t);
x = max(x, f[k-1][i1-1][i2-1] + t);
x = max(x, f[k-1][i1][i2-1] + t);
x = max(x, f[k-1][i1][i2] + t);
}
}
cout << f[n + n][n][n] << endl;
return 0;
}
练习 :方格取数的同类题 - 传纸条
https://www.acwing.com/problem/content/277/
题意
题目比较长,翻译转化之后大概就是在一个矩阵上找两条从 (1, 1) 到 (m, n) 的路径,中间没有交点,且路径数字和最大
分析
直观上感觉这题跟上一题基本上一样,除了要求不能相交。但是直觉告诉我们在上一题的答案中,不相交的路径应该是较优的,因为矩阵上的数字总是非负的,直觉上绕开一下可以拿到更多。
严格证明参考 : https://www.acwing.com/solution/content/12389/
因此在上一题的代码上小改一下就可以了。
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N][N][N], w[N][N];
int main(){
int m, n;
cin >> m >> n;
for(int i = 1; i <= m; i ++)
for(int j = 1; j <= n; j ++)
cin >> w[i][j];
for(int k = 2; k <= m + n; k ++)
for(int i1 = 1; i1 < k; i1 ++)
for(int i2 = 1; i2 < k; i2 ++){
int j1 = k - i1, j2 = k - i2;
int t = w[i1][j1];
if(i1 != i2)t += w[i2][j2];
if(j1 >=1 && j1 <= n && j2 >= 1 && j2 <= n){
int& x = f[k][i1][i2];
x = max(x, f[k-1][i1][i2] + t);
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);
}
}
cout << f[m + n][m][m] << endl;
return 0;
}
标签:路径,int,i2,cin,i1,线性,三角形,include,DP 来源: https://www.cnblogs.com/Softwarer1412/p/16302769.html