本文给出了几道数字三角形模型的题解。
数字三角形模型通常给定一个矩阵,从矩阵的左上角走到右下角,每次只能向右走或者向下走,求最大值或者最小值。
这种问题可以用f[i, j]来表示从[1, 1]走到[i, j]处的Max\Min,有状态转移方程:
f[i, j] = max(f[i - 1, j], f[i, j - 1]) + a[i, j]
如果是走两遍,就用f[k, i1, i2]表示从[1, 1]走到[i1, j1]和[i2, j2]处的Max\Min,其中i1 + j1 = k = i2 + j2。状态转移方程为:
f[k, i1, i2] = max(f[k - 1, i1 - 1, i2], f[k - 1, i1, i2 - 1], f[k - 1, i1, i2], f[k - 1, i1 - 1, i2 - 1]) + a[i, j]
本题所有题目都来自AcWing算法提高课。
文章目录
- 摘花生
- 输入格式
- 输出格式
- 数据范围
- 输入样例:
- 输出样例:
- 思路和代码
- 最低通行费
- 输入格式
- 输出格式
- 数据范围
- 输入样例:
- 输出样例:
- 样例解释
- 思路和代码
- 方格取数
- 题目背景
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 思路和代码
- 传纸条
- [NOIP2008 提高组] 传纸条
- 题目描述
- 输入格式
- 输出格式
- 样例 #1
- 样例输入 #1
- 样例输出 #1
- 提示
- 思路和代码
摘花生
1015. 摘花生 - AcWing题库
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]表示从第1行第1列,到第i行第j列,路上最多能采集到的花生数量。
因为只能往下和往右走,因此f[i, j]只能从f[i - 1, j]和f[i, j - 1]转移过来。
因此,有:
f[i, j] = max(f[i - 1, j], f[i, j - 1]) + a[i, j]
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 110;
int t, r, c;
int a[N][N];
int f[N][N];
int main() {
cin >> t;
while(t -- ) {
cin >> r >> c;
for(int i = 1; i <= r; i ++ ) {
for(int j = 1; j <= c; j ++ ) {
scanf("%d", &a[i][j]);
}
}
memset(f, 0, sizeof f);
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]) + a[i][j];
}
}
cout << f[r][c] << endl;
}
return 0;
}
最低通行费
一个商人穿过一个 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
BASIC
输出样例:
109
TEXT
样例解释
样例中,最小值为 109=1+2+5+7+9+12+19+21+33。
思路和代码
用f[i, j]表示走到[i, j]的最小花销。“商人必须在 (2N−1) 个单位时间穿越出去。”表示只能向下或者向右走。这道题和上一道摘花生类似,不同之处是这道题求的是最小值。
状态转移方程:
f[i, j] = min(f[i - 1, j], f[i, j - 1]) + a[i, j]
初始化时,因为是求最小值,要注意边界时不能取到周围的值,所以把f初始化为0x3f。状态[1, 1]要从[0, 1]或者[1, 0]转移过来,所以把f[1, 0]或者f[0, 1]设为0.
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 110;
int n;
int a[N][N];
int f[N][N];
int main() {
cin >> n;
memset(f, 0x3f, sizeof f);
for(int i = 1; i <= n; i ++ ) {
for(int j = 1; j <= n; j ++ ) {
scanf("%d", &a[i][j]);
f[i][j] = 0;
}
}
f[0][1] = 0;
for(int i = 1; i <= n; i ++ ) {
for(int j = 1; j <= n; j ++ ) {
f[i][j] = min(f[i - 1][j], f[i][j - 1]) + a[i][j];
}
}
cout << f[n][n] << endl;
return 0;
}
方格取数
[P1004 NOIP2000 提高组] 方格取数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目背景
NOIP 2000 提高组 T4
题目描述
设有 N × N N \times N N×N 的方格图 ( N ≤ 9 ) (N \le 9) (N≤9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 0 0 0。如下图所示(见样例):
某人从图的左上角的
A
A
A 点出发,可以向下行走,也可以向右走,直到到达右下角的
B
B
B 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字
0
0
0)。
此人从
A
A
A 点到
B
B
B 点共走两次,试找出
2
2
2 条这样的路径,使得取得的数之和为最大。
输入格式
输入的第一行为一个整数 N N N(表示 N × N N \times N N×N 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 0 0 0 表示输入结束。
输出格式
只需输出一个整数,表示 2 2 2 条路径上取得的最大的和。
样例 #1
样例输入 #1
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
样例输出 #1
67
提示
数据范围: 1 ≤ N ≤ 9 1\le N\le 9 1≤N≤9。
思路和代码
这道题和上面的题不同之处在于这道题是走两遍的,对于每一个格子,它上面的数字只能加一次。
假设同时走两条路,那么当第一条路走到的点[i1, j1]和第二条路走到的点[i2, j2]重合时,一定有i1 + j1 = i2 + j2.
我们设i1 + j1 = k = i2 + j2.那么k从2逐渐增加到2 * n.
设f[k, i1, i2]表示第一条路走到了点[i1, j1],第二条路走到了点[i2, j2],且i1 + j1 = k,f[k, i1, i2]表示取得的数之和的最大值。
f[k, i1, i2]可以从4个点转移过来:f[k - 1, i1, i2], f[k - 1, i1 - 1, i2], f[k - 1, i1, i2 - 1], f[k - 1, i1 - 1, i2 - 1]。即[i1, j1]是从[i1 - 1, j1]或者[i1, j1 - 1]转移过来的,[i2, j2]是从[i2 - 1, j2]或者[i2, j2 - 1]转移过来的,二者排列组合就得到了f[k, i1, i2]的转移方程。
f[k, i1, i2] = max(f[k - 1, i1 - 1, i2], f[k - 1, i1, i2 - 1], f[k - 1, i1, i2], f[k - 1, i1 - 1, i2 - 1]) + t。
其中t表示[i1, j1]和[i2, j2]两个点能取到的数。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 15;
int g[N][N];
int n;
int f[2 * N][N][N];
int main() {
cin >> n;
int a, b, c;
while(cin >> a >> b >> c, a || b || c) {
g[a][b] = c;
}
for(int k = 2; k <= 2 * n; k ++ ) {
for(int i1 = 1; i1 <= n; i1 ++ ) {
for(int i2 = 1; i2 <= n; i2 ++ ) {
int t = g[i1][k - i1];
if(i1 != i2) t += g[i2][k - i2];
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[2 * n][n][n] << endl;
return 0;
}
传纸条
[P1006 NOIP2008 提高组] 传纸条 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
[NOIP2008 提高组] 传纸条
题目描述
小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排坐成一个 m m m 行 n n n 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 ( 1 , 1 ) (1,1) (1,1),小轩坐在矩阵的右下角,坐标 ( m , n ) (m,n) (m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 0 0 0 表示),可以用一个 [ 0 , 100 ] [0,100] [0,100] 内的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。现在,请你帮助小渊和小轩找到这样的两条路径。
输入格式
第一行有两个用空格隔开的整数 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
样例输入 #1
3 3
0 3 9
2 8 5
5 7 0
样例输出 #1
34
提示
【数据范围】
对于
30
%
30\%
30% 的数据,满足
1
≤
m
,
n
≤
10
1 \le m,n \le 10
1≤m,n≤10。
对于
100
%
100\%
100% 的数据,满足
1
≤
m
,
n
≤
50
1 \le m,n \le 50
1≤m,n≤50。
【题目来源】
NOIP 2008 提高组第三题。
思路和代码
这道题和方格取数很相似,用方格取数的思路也能做。它和方格取数的不同点在于它不能经过同一个点两次。但是注意,每个点的值都是正数,它的最优解一定不可能经过一个点两次。证明链接:AcWing 275. 证明传纸条为何可以使用方格取数的代码 - AcWing
另外需要注意i1和i2的取值范围,它不能超过行数m且不能超过k。
#include<iostream>
using namespace std;
const int N = 60;
int g[N][N];
int n, m;
int f[2 * N][N][N];
int main() {
cin >> m >> n;
for(int i = 1; i <= m; i ++ )
for(int j = 1; j <= n; j ++ )
scanf("%d", &g[i][j]);
for(int k = 2; k <= m + n; k ++ ) {
for(int i1 = 1; i1 <= m && i1 < k; i1 ++ ) {
for(int i2 = 1; i2 <= m && i2 < k; i2 ++ ) {
int t = g[i1][k - i1];
if(i1 != i2) t += g[i2][k - i2];
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][m][m] << endl;
return 0;
}