😊😊 😊😊
不求点赞,只求耐心看完,指出您的疑惑和写的不好的地方,谢谢您。本人会及时更正感谢。希望看完后能帮助您理解算法的本质
😊😊 😊😊
[USACO1.5][IOI1994]数字三角形 Number Triangles
题目描述
观察下面的数字金字塔。
写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
在上面的样例中,从 7 → 3 → 8 → 7 → 5 7 \to 3 \to 8 \to 7 \to 5 7→3→8→7→5 的路径产生了最大
输入格式
第一个行一个正整数 r r r ,表示行的数目。
后面每行为这个数字金字塔特定行包含的整数。
输出格式
单独的一行,包含那个可能得到的最大的和。
样例 #1
样例输入 #1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
样例输出 #1
30
提示
【数据范围】
对于
100
%
100\%
100% 的数据,
1
≤
r
≤
1000
1\le r \le 1000
1≤r≤1000,所有输入在
[
0
,
100
]
[0,100]
[0,100] 范围内。
题目翻译来自NOCOW。
USACO Training Section 1.5
IOI1994 Day1T1
小白到进阶各种解法:
一、暴搜:😊
思路:
枚举所有从第一层到最后一层的路径,当最后一层走完的时候,与之前所有路径的最大值,进行比较然后取最值。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e3 + 10;
int g[N][N]; //三角形矩阵本身!
int n, res;
void dfs (int x, int y, int sum)
{
if (x > n){
res = max(res, sum);
return;
}
// 搜索
dfs (x+1, y, sum + g[x+1][y]);
dfs (x+1, y+1, sum + g[x+1][y+1]);
return ;
}
int main()
{
cin >> n;
//输入数字三角形矩阵!
for (int i=1; i <= n; i ++)
for (int j=1; j <= i; j ++)
cin >> g[i][j];
dfs (1, 1, g[1][1]); //从起点开始往下搜路径
cout << res << endl;
return 0;
}
二、记忆化搜索:😊
思路:
对于已经走过的点进行记录,即记录当前点到目标点的最大和值,下次再走到这个点的时候也会是一样的结果,所以直接返回,不用再递归搜索当前点的分支了,剪枝返回!
- 从起点出发开始搜索。
- 因为每个点可以有两个方向走,但是不知道哪个方向路径总和更大,所以两个方向之间取一个 m a x max max 值。
- 如何具体化一条路径的最大值呢?显然只有走到底部的时候,即整条路的走法已经确定了,回溯累加答案!
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e3 + 10;
int g[N][N]; //三角形矩阵本身!
int n, res;
int f[N][N]; //记录从点 (i, j)到达最后一层的和值。
int dfs (int x, int y)
{
if (f[x][y] != -1)
return f[x][y];
if (x == n){
return f[x][y] = g[x][y];
}
// 搜索
f[x][y] = max(dfs(x+1,y), dfs(x+1, y+1)) + g[x][y];
return f[x][y];
}
int main()
{
cin >> n;
memset (f, -1, sizeof(f));
//输入数字三角形矩阵!
for (int i=1; i <= n; i ++)
for (int j=1; j <= i; j ++)
cin >> g[i][j];
cout << dfs(1, 1);
return 0;
}
三、本题考察算法:从上到下DP – 初始化😊
即从子问题推导大问题,和记忆化搜索相比的话, D P DP DP 更类似于从从小到大,而记忆化搜索是回溯的时候更新答案。
思路:
为什么从上到下要初始化呢?
请看下图:思考红色圈是由哪些状态转移而来的!
由于本题的输入不是一个规则输入,而是直角三角形的形式输入的,所以说对于边界上的点,转移状态可能取到为空,有人说,空的不是初始值为0吗?为何会初始化一个负无穷大呢?因为本题的数据也可能是负数,所以只能初始化为负无穷,从而保证状态的转移正确!
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e3 + 10;
int g[N][N];
int f[N][N]; //从起点到当前点的最大权值和!
int n;
int main()
{
cin >> n;
for (int i=1; i <=n ; i ++)
for (int j=1; j <= i; j ++)
cin >> g[i][j];
memset (f, -0x3f, sizeof(f)); //为什么不赋初值为0呢?因为g[i][j]有可能为负数
f[1][1] = g[1][1];
for (int i=2; i <= n; i ++)
{
for (int j=1; j <= i; j ++)
{
f[i][j] = max(f[i-1][j], f[i-1][j-1]) + g[i][j];
}
}
int res = -1e8;
for (int i=1; i <= n; i ++)
res= max(res, f[n][i]);
cout << res << endl;
return 0;
}
四、本题考察算法:从下到上DP – 无初始化😊
思路:
由图可知,我们从下往上转移状态的时候,针对于其中的边界上的点,其状态的转移是不会出现越界的!所以无需初始化!
目标求:
m
a
x
(
f
[
1
]
[
1
]
)
max(f[1][1])
max(f[1][1])
但是有同学会问:那我们代码里从第n层递推过来的啊,且递推公式如下:
f[i][j] = max(f[i+1][j], f[i+1][j+1]) + g[i][j];
那
f
[
i
+
1
]
,
f
[
j
+
1
]
f[i+1],f[j+1]
f[i+1],f[j+1] 不就越界了吗?为什么不赋值为负无穷呢?
答:就是赋值为0,请注意:
m
a
x
后面的
g
[
i
]
[
j
]
max后面的 g[i][j]
max后面的g[i][j],只有
g
[
i
]
[
j
]
g[i][j]
g[i][j] 的存在,当
f
[
i
+
1
]
[
j
]
,
f
[
i
+
1
]
[
j
+
1
]
f[i+1][j], f[i+1][j+1]
f[i+1][j],f[i+1][j+1]都取0时,此时最后一行就等于初值!是符合题意和正确的答案!
代码:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e3 + 10;
int f[N][N];
int g[N][N];
int n;
int main()
{
cin >> n;
for (int i=1; i <= n; i ++)
for (int j=1; j <= i; j ++)
cin >> g[i][j];
//从下往上递推!
for (int i=n; i>=1; i--)
for (int j=1; j<=i; j++)
f[i][j] = max(f[i+1][j], f[i+1][j+1]) + g[i][j];
cout << f[1][1] << endl;
return 0;
}