【DP学习总结】数字三角形模型

数字三角形模型:

描述:有一个数塔:
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[i1][j],f[i1][j1])+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:最低通行费

题目链接:Acwing 1018

题意:

给出一个 N ∗ N N * N NN的网格,从 ( 1 , 1 ) (1,1) (1,1)进入到 ( n , n ) (n,n) (n,n)出去,每走一次消耗1单位时间,规定在 2 N − 1 2N-1 2N1单位时间内走出,且费用最少。可以向四周走(上下左右,但不能走出网格)

solution:

首先,要从 ( 1 , 1 ) (1,1) (1,1)进入到 ( n , n ) (n,n) (n,n)出去,且时间为 2 N − 1 2N-1 2N1,那么也就说每次只能往下或者往右。那么就可以转换成一个数字三角形模型了。

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: 方格取数

题目链接:Acwing 1027

题意:

设有 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<<nn]。而题目只给出了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[k1][i1][j1],f[k1][i1][j],f[k1][i][j1],f[k1][i][j])+wi,ki

如果 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. 传纸条

题目链接:Acwing 275

题意:

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";
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

W⁡angduoyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值