数字三角形模型:
描述:有一个数塔:
1
3
2
4
5
4
6
7
7
7
6
1 \\ 3 \quad 2\\ 4 \quad 5 \quad 4 \\ 6 \quad 7\quad 7 \quad7\quad 6
13245467776
一开始在最顶上,每次可以下方相邻的地方走,问从顶走到底的最大值/最小值
做法:
考虑动态规划:
- 状态表示: f [ i ] [ j ] f[i][j] f[i][j]表示从 ( 1 , 1 ) − > ( i , j ) (1,1)->(i,j) (1,1)−>(i,j)的所有方案中的最大/最小值
- 状态计算: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − 1 ] ) + a [ i ] [ j ] f[i][j] = max(f[i - 1][j], f[i-1][j-1])+a[i][j] f[i][j]=max(f[i−1][j],f[i−1][j−1])+a[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]) + a[i][j];
}
}
例题1:最低通行费
题意:
给出一个 N ∗ N N * N N∗N的网格,从 ( 1 , 1 ) (1,1) (1,1)进入到 ( n , n ) (n,n) (n,n)出去,每走一次消耗1单位时间,规定在 2 N − 1 2N-1 2N−1单位时间内走出,且费用最少。可以向四周走(上下左右,但不能走出网格)
solution:
首先,要从 ( 1 , 1 ) (1,1) (1,1)进入到 ( n , n ) (n,n) (n,n)出去,且时间为 2 N − 1 2N-1 2N−1,那么也就说每次只能往下或者往右。那么就可以转换成一个数字三角形模型了。
Code:
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n; cin >> n;
vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
cin >> dp[i][j];
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
{
if(i == 1) dp[i][j] += dp[i][j - 1];
else if(j == 1) dp[i][j] += dp[i - 1][j];
else dp[i][j] += min(dp[i - 1][j], dp[i][j - 1]);
}
cout << dp[n][n] << endl;
}
例题2: 方格取数
题意:
设有 N × N N×N N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:
某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点。
在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。
solution:
乍一看,又是一道数字三角形模型的题目。唯一的区别就是,需要走两次,且第二次走的不再计算。很容易想到用一个四维数组
f
[
x
1
]
[
y
1
]
[
x
2
]
[
y
2
]
f[x_1][y_1][x_2][y_2]
f[x1][y1][x2][y2]来分别表示两条路径,但是由于第二次走的时候不会再计算,这时应该还要加一维来表示状态
f
[
x
1
]
[
y
1
]
[
x
2
]
[
y
2
]
[
1
<
<
n
∗
n
]
f[x_1][y_1][x_2][y_2][1 << n * n]
f[x1][y1][x2][y2][1<<n∗n]。而题目只给出了64MB的内存,显然是不行的。
那我们转换一个思路,我们假设两条路径一起出发,对于每条路径来说,每走一次,无非就是往下或者往右,那么他们的
x
+
y
x + y
x+y的值是一个定值的。也就是:
x
1
+
y
1
=
x
2
+
y
2
=
k
x_1 + y_1 = x_2 + y_2 = k
x1+y1=x2+y2=k这样的话,我们就可以用三维数组来表示了,初始状态:f[2][1][1]
,最终状态:f[2*n][n][n]
- 状态表示: f [ k ] [ i ] [ j ] f[k][i][j] f[k][i][j] x 1 + y 1 x_1+y_1 x1+y1为 k k k, x 1 = i , x 2 = j x_1=i,x_2=j x1=i,x2=j的所有方案当中的最大值
- 状态计算: f [ k ] [ i ] [ j ] = m a x ( f [ k − 1 ] [ i − 1 ] [ j − 1 ] , f [ k − 1 ] [ i − 1 ] [ j ] , f [ k − 1 ] [ i ] [ j − 1 ] , f [ k − 1 ] [ i ] [ j ] ) + w i , k − i f[k][i][j]=max(f[k-1][i-1][j-1],f[k-1][i-1][j],f[k-1][i][j-1],f[k - 1][i][j]) + w_{i,k-i} f[k][i][j]=max(f[k−1][i−1][j−1],f[k−1][i−1][j],f[k−1][i][j−1],f[k−1][i][j])+wi,k−i
如果 i = = j i==j i==j,那说明走到了相同的格子了,不能重复加
Code:
#include <bits/stdc++.h>
using namespace std;
const int N = 12;
int w[N][N], n, a, b, c;
int f[N << 1][N][N];
inline void Max(int &a, int b) {
if(a < b) a = b;
}
int main()
{
cin >> n;
while(cin >> a >> b >> c, a || b || c) w[a][b] += c;
for(int k = 2; k <= n * 2; k ++) {
for(int i= 1; i <= n; ++ i) {
for(int j = 1; j <= n; ++ j) {
if(k - i <= 0 || k - i > n || k - j <= 0 || k - j > n) continue;
int v = w[i][k - i];
if(i != j) v += w[j][k - j];
int &t = f[k][i][j];
Max(t, f[k - 1][i - 1][j - 1] + v);
Max(t, f[k - 1][i - 1][j] + v);
Max(t, f[k - 1][i][j - 1] + v);
Max(t, f[k - 1][i][j] + v);
}
}
}
cout << f[2 * n][n][n] << "\n";
}
例题3. 传纸条
题意:
n × m n × m n×m的网格,需要从 ( 1 , 1 ) − > ( n , m ) (1,1)->(n,m) (1,1)−>(n,m) 并且又从 ( n , m ) − > ( 1 , 1 ) (n,m)->(1,1) (n,m)−>(1,1),两条路径上值加起来最大。同样的相同的位置只会计算一次。
solution:
此题可以转化为上题,就是从
(
1
,
1
)
−
>
(
n
,
m
)
(1,1)->(n,m)
(1,1)−>(n,m)走两次的最大值。并且,这两条路是不会相交的
具体的证明:证明为何不会有交点
对上题代码进行稍作修改即可AC此题啦~
Code:
#include <bits/stdc++.h>
using namespace std;
const int N = 55;
int w[N][N], n, m;
int f[N << 1][N][N];
inline void Max(int &a, int b) {
if(a < b) a = b;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++ i) {
for(int j = 1; j <= m; ++ j) {
cin >> w[i][j];
}
}
for(int k = 2; k <= n + m; k ++) {
for(int i= 1; i <= n; ++ i) {
for(int j = 1; j <= n; ++ j) {
if(k - i <= 0 || k - i > m || k - j <= 0 || k - j > m) continue;
int v = w[i][k - i];
if(i != j) v += w[j][k - j];
int &t = f[k][i][j];
Max(t, f[k - 1][i - 1][j - 1] + v);
Max(t, f[k - 1][i - 1][j] + v);
Max(t, f[k - 1][i][j - 1] + v);
Max(t, f[k - 1][i][j] + v);
}
}
}
cout << f[n + m][n][n] << "\n";
}