当前位置: 首页 > news >正文

数据结构与算法学习笔记(Acwing提高课)----动态规划·数字三角形

数据结构与算法学习笔记----动态规划·数字三角形

@@ author: 明月清了个风
@@ first publish time: 2025.4.23

ps⭐️终于开始提高课的题啦,借的人家的号看,以后给y总补票叭,提高课的题比之前的多很多啊哈哈哈哈,基本上每种题型都对应了难度逐步上升的几道题,和基础课的题相比加了一层应用,需要从题目中抽象出模型才能解题。

数字三角形的题为动态规划的经典模型,基础课中已经讲过了,在这里。提高课的是在此基础上的进一步延伸和应用,但原理其实没有变。

Acwing 1015. 摘花生

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

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

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

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

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

1.gif

输入格式

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

接下来是 T T T组数据。

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

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

输出格式

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

数据范围

1 ≤ T ≤ 100 1 \le T \le 100 1T100

1 ≤ R , C ≤ 100 1 \le R,C \le 100 1R,C100

0 ≤ M ≤ 1000 0 \le M \le 1000 0M1000

思路

对于动态规划而言,和基础课一样,考虑状态表示和状态转移。

对于状态表示,使用f[i][j],表示的集合是所有从(1, 1)走到(i, j)的路线,而要求的属性是这个集合中的最大值。

对于状态转移或者说状态计算,就是将上述集合进行划分,找出其子集递归求解。而状态划分的很重要的一种方法就是根据最后一步操作进行划分,也就是f[i][j]是怎么来的这一步。在这一题中,f[i][j]可以是从上面下来,也可以是从左边过来,因此可以划分为两个子集f[i][j - 1]f[i - 1][j]。需要注意的是,对于集合的划分,最重要的两个原则是不重不漏,其中不漏是任何情况都需要满足的,因为每种方案都需要考虑,而不重在求最大值或最小值时可以不满足,因为重复的计算并不会提高集合中的最值。

在上面已经将集合f[i][j]划分为两个集合f[i - 1][j]f[i][j - 1],那么求f[i][j]的最大值就是求这两个子集的最大值再加上最后一步的权值即可。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 110;int T;
int n, m;
int a[N][N];
int f[N][N];int main()
{cin >> T;while(T --){cin >> n >> m;for(int i = 1; i <= n; i ++)for(int j = 1; j <= m; j ++)cin >> 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];cout << f[n][m] << endl;}return 0;
}

Acwing 1018. 最低通行费

一个商人穿过一个 N × N N \times N N×N的正方形的网格,去参加一个非常重要的商务活动。他要从网格的左上角进,右下角出。每穿越中间1个小方格,都要花费1个单位时间。商人必须在 2 N − 1 2N - 1 2N1个单位时间穿越出去。而在经过中间的每个小方格时,都需要缴纳一定的费用。
这个商人期望在规定时间内用最少费用穿越出去。请问至少需要多少费用?
注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。

输入格式

第一行是一个整数 T T T,表示正方形的宽度 N N N

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

输出格式

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

数据范围

1 ≤ N ≤ 100 1 \le N \le 100 1N100

思路

其实还是和数字三角形一样的模型,只是多了个迷惑的地方,给了步骤限制为 2 N − 1 2N - 1 2N1,但是可以发现只要不回头走就行。因此思路和代码都是和上面一样的。

需要注意的地方时,这一题求的集合的属性是最小值,因此需要对边界的地方进行特判和初始化。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 110, inf = 1e9;int n;
int f[N][N];
int a[N][N];int main()
{cin >> n;for(int i = 1; i <= n; i ++)for(int j = 1; j <= n; j ++)cin >> a[i][j];for(int i = 1; i <= n; i ++)for(int j = 1; j <= n; j ++){if(i == 1 && j == 1) f[i][j] = a[i][j];else{f[i][j] = inf;if(i > 1) f[i][j] = min(f[i][j], f[i - 1][j] + a[i][j]);if(j > 1) f[i][j] = min(f[i][j], f[i][j - 1] + a[i][j]);}}cout << f[n][n] << endl;return 0;
}

Acwing 1027. 方格取数

设有N*N的方格图( N ≤ 10 N \le 10 N10),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。
某人从图的左上角的 A A A ( 1 , 1 ) (1, 1) (1,1)出发,可以向下行走,也可以向右走,直到到达右下角的B点 ( N , N ) (N, N) (N,N)。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从 A A A点到 B B B点共走两次,试找出2条这样的路径,使得取得的数之和为最大。

输入格式

第一行是一个整数 N N N,表示 N × N N \times N N×N的方格图。

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

一行以"0 ,0 ,0"表示结束。

输出格式

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

数据范围

$ N \le 10$

思路

这一题的变化是要走两次。对于上面两题而言都只需要计算一个路径,而走两次最大的变化就是同一个数只能被拿走一次。

首先思考是两条路线一起走还是两条路线先后走,因为每个数只能拿一次,因此两条路线是完全独立的两条线,先走后走并不影响总和。可以直接对上面的状态表示进行推广,使用f[i1][j1][i2][j2]表示第一条路线走到了a[i1][j1],第二条路线走到了a[i2][j2]的集合。

然后就是如何不重复走到同一个格子,因为不能回头走,所以如果两条线走到了同一个格子,那么肯定有 i 1 + j 1 = i 2 + j 2 i_1 + j_1 = i_2 + j_2 i1+j1=i2+j2

因此就有了下面的优化,上面这个状态表示是四维的,虽然数据范围很小,但是仍有优化空间,考虑到要对每个格子的坐标的和作比较,因此使用f[k][i1][i2]进行表示,其中k表示横纵坐标的和,那么就有j1 = k - i1j2 = k - i2,当i1 == i2时,可能出现两条路线重合。

对于状态划分来说,和之前的题目一样,使用最后一步进行划分,只是这里会有两条路线。因此可以根据第一条路线向右或向下,第二条路线向右或向下分为四类:下下,下右,右下,右右。

以下下的组合为例说明状态计算方法:

  • 对于下下的组合来说,两条路线都是向下走到了最新的一步,因此是分别从a[i1][j1 - 1]a[i2][j2 - 1]走到了最新位置,这两个地方的状态可以由 f [ k − 1 ] [ i 1 ] [ i 2 ] f[k - 1][i1][i2] f[k1][i1][i2]表示,然后判断一下是否重合即可,重合的话那就只加上一个a[i1][j1]即可,不重合就要同时加上a[i1][j1]a[i2][j2]

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 20;int n;
int f[N * 2][N][N];
int w[N][N];int main()
{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 <= 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);}}cout << f[n + n][n][n] << endl;return 0;
}

Acwing 275. 传纸条

小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。

一次素质拓展活动中,班上同学安排坐成一个 m m m n n n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。

幸运的是,他们可以通过传纸条来进行交流。

纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 ( 1 , 1 ) (1, 1) (1,1),小轩坐在矩阵的右下角,坐标 ( m , n ) (m, n) (m,n)

从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。

在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。

班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙,反之亦然。

还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 0 0 0表示),可以用一个 0 ∼ 100 0 \sim 100 0100的自然数来表示,数越大表示越好心。

小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。

现在,请你帮助小渊和小轩找到这样的两条路径。

输入格式

第一行有 2 2 2个用空格隔开的整数 m m m n n n,表示学生矩阵有 m m m n n n列。

接下来 m m m行是一个 m × n m \times n m×n的矩阵,矩阵中第 i i i j j j列的整数表示坐在第 i i i j j j列的学生的好心程度,每行的 n n n个整数之间用空格隔开。

输出格式

输出一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

数据范围

1 ≤ n , m ≤ 50 1 \le n, m \le 50 1n,m50

思路

这一题与上一题的区别是同一个格子不能走第二次,在上一题中,如果走到了重合的格子会少加一次权重,而这里要求是不能走第二次,其实也很好解决,也就是要证明有两条不相交的路线具有最大的好心程度之和。

首先要解决的是两条路线相交的问题,如果存在两条相交的路线,如下图所示,从A到B的两条路线中有两个交点C与D。

图1

因为经过的格子的权重是确定的,因此可以对两条路线上的部分片段进行交换,变换后入下图:

在这里插入图片描述

因此得证相交的路线可以通过路线的变形使其变成等效但仅有相交点而不相错的情况。

根据题意,同一个点不能走第二次,因此考虑如何处理相交点 C C C D D D。通过观察上图可以发现,对于重合点C来讲,该路线可以转化为下图所示经过点E的路线,且该路线的好心程度之和一定优于原路线(将重合点C权重清零后第二次不再计算)

在这里插入图片描述

根据上一题的代码可以发现,其实当走到同一点时,我们只会添加一次权重,因此这里的值一定会比经过点E的路线值小,也就是题目所限制的非法路线算出来的值一定小于最优解合法路的值,因此上一题的代码中不必为了这一题进行修改,代码稍有变动。

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;const int N = 60;int n, m;
int f[N * 2][N][N];
int w[N][N];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 i1 = 1; i1 <= n; i1 ++)for(int i2 = 1; i2 <= n; i2 ++){int j1 = k - i1, j2 = k - i2;if(j1 >= 1 && j1 <= m && j2 >= 1 && j2 <= m){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);}}cout << f[n + m][n][n] << endl;return 0;
}

相关文章:

  • Kubernetes (k8s) 日常运维命令总结
  • 12-DevOps-Gitlab托管Jenkinsfile
  • TDengine 数据订阅设计
  • Linux驱动开发2 - 内核定时器驱动
  • 时序数据库 TDengine 助力石油石化业务, 平滑接替 Oracle 数据库
  • 【GESP】C++二级真题 luogu-B4259 [GESP202503 二级] 等差矩阵
  • Spark集群搭建之Yarn模式
  • PyCharm 链接 Podman Desktop 的 podman-machine-default Linux 虚拟环境
  • LeetCode-46. 全排列
  • 1、AI及LLM基础:OpenAI 开发
  • NoSql文档型数据库——Mongodb
  • Apache中间件解析漏洞与安全加固
  • Python流程控制
  • FANUC机器人GI与GO位置数据传输设置
  • 初识Redis · 事务
  • 项目组合管理PPM
  • 5.4.云原生与服务网格
  • uniapp返回上一页接口数据更新了,页面未更新
  • Lua 第9部分 闭包
  • 官方不存在tomcat10-maven-plugin插件
  • 央行副行长:上海国际金融中心建设是我国参与国际金融竞争的核心载体
  • 蔚来李斌:当下国际贸易环境有不确定性,但坚信中国汽车产业最终将占全球四成份额
  • 上金所:调整黄金、白银延期部分合约保证金水平和涨跌停板
  • 工程院院士应汉杰不再担任苏州大学校长
  • 格力电器:选举董明珠为公司第十三届董事会董事长
  • 日方炒作中国社会治安形势不佳,外交部:政治操弄意图明显