算法日记—动态规划之数字三角形模型

算法日记—动态规划之数字三角形模型

动态规划一直是算法竞赛中最为经典的问题,这篇博客将讲解动态规划问题中的数字三角形模型。
会以acwing上的例题作为对象。
之后会专门对dp的思考模式写一篇。

经典模板题-acwing896数字三角形

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

       7
     3   8
   8   1   0
 2   7   4    4
4  5    2    6   5

输入格式
第一行包含整数n,表示数字三角形的层数。

接下来n行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。

输出格式
输出一个整数,表示最大的路径数字和。

数据范围
1≤n≤500,
−10000≤三角形中的整数≤10000
输入样例
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例
30

思考方式
这里我们的思考方式是从集合的角度进行分析,之后会对此分析方式进行细讲。
如图:从集合的角度分析

那么如上这样分析下来 ,问题就迎刃而解了。
代码如下

//    aconacon
#include<iostream>
#include<algorithm>
using namespace std;
const int N=510,inf=1e9;
int f[N][N];
int a[N][N];
int n;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
            cin>>a[i][j];
    for(int i=0;i<=n;i++)
        for(int j=0;j<=i+1;j++)
            f[i][j]=-inf;
    f[1][1]=a[1][1];
    for(int i=2;i<=n;i++)
        for(int j=1;j<=i;j++)
            f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
    int res=-inf;
    for(int i=1;i<=n;i++)res=max(res,f[n][i]);
    cout<<res<<endl;
    return 0;
}

问题升级 -acwing1015 摘花生

Hello Kitty想摘点花生送给她喜欢的米老鼠。

她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。

地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。

Hello Kitty只能向东或向南走,不能向西或向北走。

问Hello Kitty最多能够摘到多少颗花生。

摘花生问题

输入格式
第一行是一个整数T,代表一共有多少组数据。

接下来是T组数据。

每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。

每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。

输出格式
对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。

数据范围
1≤T≤100,
1≤R,C≤100,
0≤M≤1000
输入样例

2
2 2
1 1
3 4
2 3
2 3 4
1 6 5

输出样例

8
16

思考方式
这道题的难点在于题目是抽象的,我们需要读懂题意,从其中挖掘出信息。我们会发现这道题本质上与上题是没有任何区别的。是个潜在的数字三角形模型。从集合角度出发,最后的状态可由上面或左边状态转移;所以我们状态转移方程可写成:

//摘花生的状态转移方程
 f[i][j]=max(f[i-1][j],f[i][j-1])+w[i][j];
 //w为[i][j]的花生数量;f为到[i][j]时已拿的花生最大值。

代码如下

// aconacon
#include<iostream>
#include<algorithm>
using namespace std;
int T;
const int N=101;
int f[N][N];
int w[N][N];

int main(){
    cin>>T;
    while(T--){
        int r,c;
        cin>>r>>c;
        for(int i=1;i<=r;i++)
            for(int j=1;j<=c;j++)
                cin>>w[i][j];
        for(int i=1;i<=r;i++)
            for(int j=1;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;
}

再升级—acwing1018最低通行费

一个商人穿过一个N×N的正方形的网格,去参加一个非常重要的商务活动。

他要从网格的左上角进,右下角出。

每穿越中间1个小方格,都要花费1个单位时间。

商人必须在(2N-1)个单位时间穿越出去。

而在经过中间的每个小方格时,都需要缴纳一定的费用。

这个商人期望在规定时间内用最少费用穿越出去。

请问至少需要多少费用?

注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。

输入格式
第一行是一个整数,表示正方形的宽度N。

后面N行,每行N个不大于100的整数,为网格上每个小方格的费用。

输出格式
输出一个整数,表示至少需要的费用。

数据范围
1≤N≤100
输入样例

5
1  4  6  8  10 
2  5  7  15 17 
6  8  9  18 20 
10 11 12 19 21 
20 23 25 29 33

输出样例
109
样例解释

样例中,最小值为109=1+2+5+7+9+12+19+21+33。

思考方式
此题中注意要在(2N-1)时间内走完。也就是只能走2N-1步。由于是NxN的矩阵。所以我们是不能回头的,也就是不能向左或向上,只能向下和向右,这是隐藏信息不然会被题目带跑。
第二点就是此题是最小通行费,从集合角度思考我们要的是min值。所以会存在一个边界问题。因为数组初始值会影响我们取值。所以我们要在第一行和第一列以及左上角第一个进行特判一下。本质呢也还是我们的数字三角形模型。

//aconacon
#include<iostream>
#include<algorithm>
using namespace std;
int INF=1e8;
const int N=101;
int f[N][N];
int 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];
        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]);   // 只有不在第一列的时候,才可以从左边过来
            }
        cout<<f[n][n]<<endl;
    
    return 0;
}

如果是同时求两个状态方案和最大呢-acwing1027方格取数

设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:

在这里插入图片描述

某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点。

在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。

此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。

输入格式
第一行为一个整数N,表示 N×N 的方格图。

接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。

行和列编号从 1 开始。

一行“0 0 0”表示结束。

输出格式
输出一个整数,表示两条路径上取得的最大的和。

数据范围
N≤10
输入样例

8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0

输出样例
67
思考方式
在这里插入图片描述

代码如下

//aconacon
#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;
}

本篇最后

作为新人上路 本篇有很多细节也许没讲清楚或者会有错误,希望大家多多评论指错题建议,小王会及时修改的,之后有好的题目会继续更新此文章。希望通过此篇能让你对数字三角形模型有新的理解,让我们快乐dp吧。我们下篇见。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值