路径类dp是线性dp的⼀种,它是在⼀个n×m的矩阵中设置⼀个⾏⾛规则,研究从起点⾛到终点的⽅案数、最⼩路径和或者最⼤路径和等等的问题
矩阵的最小路径和_牛客题霸_牛客网

- 状态表⽰:
dp[i][j]
表⽰:到达[i, j]
位置处,最⼩路径和是多少。
那我们的最终结果就是dp[n][m]
。 - 状态转移:
到达[i, j]
位置之前的⼀⼩步,有两种情况:
i. 从[i - 1, j]
向下⾛⼀步,转移到[i, j]
位置;
ii. 从[i, j - 1]
向右⾛⼀步,转移到[i, j]
位置。
由于到[i, j]
位置两种情况,并且我们要找的是最⼩路径,因此只需要这两种情况下的最⼩值,再加上[i,j]
位置上本⾝的值即可:dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + a[i][j]
。 - 初始化:
第⼀⾏和第⼀列是要初始化的,因为会越界访问。
但是如果把整张表初始化为⽆穷⼤,然后把dp[0][1]
和dp[1][0]
的值设为0,后续填表就是正确的。 - 填表顺序:
根据「状态转移⽅程」的推导来看,填表的顺序就是「从上往下」填每⼀⾏,每⼀⾏「从左往
后」
c++
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m;
int f[N][N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
//初始化
memset(f, 0x3f, sizeof f);
f[0][1] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
int x; cin >> x;
f[i][j] = min(f[i-1][j], f[i][j-1]) + x;
}
}
cout << f[n][m] << endl;
return 0;
}
迷雾森林
- 状态表⽰:
f[i][j]
表⽰:到达[i, j]
位置时,有多少种⽅案。
那么f[1][m]
就是我们要的结果。 - 状态转移⽅程:
a. 如果[i, j]
位置是空地,到达[i, j]
位置有两种⽅式:
- 从
[i + 1, j]
向上⾛⼀步,此时的⽅案数为f[i + 1][j]
; - 从
[i, j - 1]
向右⾛⼀步,此时的⽅案数为f[i][j - 1]
。
两者总和就是到达[i, j]
位置的总⽅案数。
b. 如果[i, j]
位置是树,⽆法⾛到,f[i][j] = 0
。
- 初始化:
可以在原始矩阵的规模上多加上⼀⾏和⼀列,把f[n + 1][1]
或者f[n][0]
初始化为1,这样后
续填表就会有意义。 - 填表顺序:
从下往上每⼀⾏,每⼀⾏从左往右
c++
#include <bits/stdc++.h>
using namespace std;
const int N = 3010, MOD = 2333;
int n, m;
int a[N][N];
int f[N][N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
//初始化
f[n][0] = 1;
for (int i = n; i >= 1; i--)
for (int j = 1; j <= m; j++)
if (a[i][j] == 0)
f[i][j] = (f[i][j-1] + f[i+1][j]) % MOD;
cout << f[1][m] << endl;
return 0;
}
P1002 [NOIP 2002 普及组] 过河卒 - 洛谷

- 状态表⽰:
f[i][j]
表⽰:到达[i, j]
位置的⽅案数。
那么f[n][m]
就是我们要的结果。 - 状态转移⽅程:
a. 如果[i, j]
位置能⾛到,到达[i, j]
位置之前的⼀⼩步,有两种情况:
- 从
[i - 1, j]
向下⾛⼀步,⾛到[i, j]
,此时的⽅案数为f[i - 1][j]
; - 从
[i, j - 1]
向右⾛⼀步,⾛到[i, j]
,此时的⽅案数为f[i][j - 1]
;
那么总⽅案数f[i][j] = f[i - 1][j] + f[i][j - 1]
。
b. 如果[i, j]
位置⾛不到,f[i][j] = 0
。
- 初始化:
我们可以给原始的矩阵多加⼀⾏多加⼀列,n, m, x, y全部+1 ,这样填任何⼀个位置都不会越
界。
然后初始化f[1][0] = 1
或者f[0][1] = 1
,保证后续填表正确即可。 - 填表顺序:
从上往下每⼀⾏,每⼀⾏从左往右
c++
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 25;
int n, m, a, b;
LL f[N][N];
bool check(int i, int j)
{
return (i == a && j == b) || (i != a && j != b && abs(i - a) + abs(j - b) == 3);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m >> a >> b;
n++; m++; a++; b++;
//初始化
f[0][1] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
if (check(i, j)) continue;
f[i][j] = f[i-1][j] + f[i][j-1];
}
cout << f[n][m] << endl;
return 0;
}
P1004 [NOIP 2000 提高组] 方格取数 - 洛谷

贪⼼+两次dp是错误的,因为两次最优不等于全局最优,可以举出反例。正解应该是同时去⾛两条路,两者相互影响,所以放在⼀起考虑。
- 状态表⽰:
需要知道当前这两条路径⾛到什么位置,因此需要四维 f [ i 1 ] [ j 1 ] [ i 2 ] [ j 2 ] f[i_{1}][j_{1}][i_{2}][j_{2}] f[i1][j1][i2][j2]来表⽰第⼀条路⾛到 [ i 1 ] [ j 1 ] [i_{1}][j_{1}] [i1][j1]第⼆条路⾛到 [ i 2 ] [ j 2 ] [i_{2}][j_{2}] [i2][j2] 。
但是我们发现,因为两者是同时出发的,所以横纵坐标之和是⼀个定值。也就是说,只要知道了横纵坐标之和,以及两者的横坐标,就可以计算出纵坐标,状态表⽰就可以优化掉⼀维。
优化后的状态表⽰: f [ s t ] [ i 1 ] [ i 2 ] f[st][i_{1}][i_{2}] f[st][i1][i2]表⽰:第⼀条路在 [ i 1 , s t − i 1 ] [i_{1},st-i_{1}] [i1,st−i1] ,第⼆条路在 [ i 2 , s t − i 2 ] [i_{2},st-i_{2}] [i2,st−i2]时,两者的路径最⼤和。那我们的最终结果就是 f [ n × 2 ] [ n ] [ n ] f[n \times 2][n][n] f[n×2][n][n] 。 - 状态转移⽅程:
第⼀条路可以从上 [ i 1 − 1 , s t − i 1 ] [i_{1}-1,st-i_{1}] [i1−1,st−i1]或者左 [ i 1 , s t − i 1 − 1 ] [i_{1},st-i_{1}-1] [i1,st−i1−1]⾛到 [ i 1 , s t − i 1 ] [i_{1},st-i_{1}] [i1,st−i1]位置;第⼆条路可
以从上 [ i 2 − 1 , s t − i 2 ] [i_{2}-1,st-i_{2}] [i2−1,st−i2]或者左 [ i 2 , s t − i 2 − 1 ] [i_{2},st-i_{2}-1] [i2,st−i2−1]⾛到 [ i 2 , s t − i 2 ] [i_{2},st-i_{2}] [i2,st−i2]位置。排列组合⼀下⼀共4中情况,分别是:
- 上+上,此时的最⼤和为:
f[st - 1][i1 - 1][i2 - 1]
; - 上+左,此时的最⼤和为:
f[st - 1][i1 - 1][i2]
; - 左+上,此时的最⼤和为:
f[st - 1][i1 2 ][i - 1]
; - 左+左,此时的最⼤和为:
f[st - 1][i1 2 ][i ]
;
取上⾯四种情况的最⼤值,然后再加上a[i1][j1]
和a[i2][j2]
。但是要注意,如果两个路径当前在同⼀位置时,只⽤加上⼀个a[i1][j1]
即可
- 初始化:
算的是路径和,0 不会影响最终结果,直接填表。 - 填表顺序:
先从⼩到⼤循环横纵坐标之和,然后依次从⼩到⼤循环两者的横坐标
c++
#include <bits/stdc++.h>
using namespace std;
const int N = 15;
int n;
int a[N][N];
int f[N*2][N][N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
int x, y, w;
while (cin >> x >> y >> w, x)
{
a[x][y] = w;
}
for (int s = 2; s <= n+n; s++)
{
for (int i1 = 1; i1 <= n; i1++)
{
for (int i2 = 1; i2 <= n; i2++)
{
int j1 = s - i1, j2 = s - i2;
if (j1 <= 0 || j1 > n || j2 <= 0 || j2 > n) continue;
int t = f[s-1][i1][i2];
t = max(t, f[s-1][i1][i2-1]);
t = max(t, f[s-1][i1-1][i2]);
t = max(t, f[s-1][i1-1][i2-1]);
if (i1 == i2)
{
f[s][i1][i2] = t + a[i1][j1];
}
else
{
f[s][i1][i2] = t + a[i1][j1] + a[i2][j2];
}
}
}
}
cout << f[n+n][n][n] << endl;
return 0;
}