前言
这道例题通过看大佬的解题思路本人获益良多,包括但不限于再次理解二分算法、地图中的深度递归dfs、数值读取函数。
真的很感谢大佬的题解分享,在这里我也将这道题分享出去,以便大家参考和自我复习。
一、题目
提炼+分析:第一行和最后一行的房间进入没有伤害,只要找到一条从第一行任意位置到最后一行任意位置的路径,且这条路经上的房间最大伤害值是所有路径里最小的即可。
二、思路
这里除去输入输出以外,需要三个部分功能共同完成:
1.深度递归dfs
需要从地图的左上角(1,1)向四个方向分别探索、递归,难点在于边界的把控和方向选择的方式,以及需要访问数组vis共同完成
2.检查函数check
需要接收二分的数值并判断能否以这个值为最低伤害找到这样一条路,如果能则减小范围二分找伤害更大的值,反之亦然
3.二分算法
个人认为难点在于找什么进行二分,例如我之前用地图范围来二分发现没有思路,所以二分的选择很关键;在一个就是二分的范围要精准把控,以免造成数据漏选。
4.组合
- 二分算法枚举出最大伤害值 to 检查函数
- 检查函数调用深度递归dfs判断是否存在以该值为最大伤害的路径
- 根据检查函数返回的结果更改二分的范围,若存在则向更小的范围二分,否则向更大的范围递归
- 最终得到最优解
三、各功能具体实现
1.数值读取函数
这一点真的是这次的主要收获之一,该函数通过先判断是否是0~9的数字,再判断负号等进行数据的读取,一下是实现代码:
int read() {
int s = 0, w = 1;
char ch = getchar();
//isdigit函数用来输入检测是否是0--9的数字,如果不是就检查是否是负号用w的正负来控制,如果是则读取数值
while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); }
while (isdigit(ch)) { s = s * 10 + ch - '0'; ch = getchar(); }
return s * w;
}
其中s用来记录数值大小,w用来确定正负。
2.深度递归函数
由于之前的深度递归都只在二叉树上实现,这次在迷宫类中实现刚开始就有点抓瞎了,其实也只是从两个方向的选择拓展到四个,关键在于大佬的方向选择方法让我眼前一亮:
int n, m, map[N][N], l, r, mid, x, y;//l和r是二分范围,x和y是地图坐标
int dx[4] = { -1,1,0,0 }, dy[4] = { 0,0,-1,1 };//x和y同时作用相当于左、右、下、上
bool flag = 0, vis[N][N];//flag判断是否能到达终点 vis记录该房间是否访问过了
void dfs(int xx, int yy) {
//如果到达最后一行,说明存在这样一条路径
if (xx == n) { flag = 1; return; }
//对四个方向进行访问
for (int i = 0; i < 4; i++) {
x = xx + dx[i]; y = yy + dy[i];//更新地图坐标
//如果范围在地图内 且 伤害值低于mid ,继续向四周递归
if (x >= 1 && x <= n && y >= 1 && y <= m && map[x][y] <= mid && !vis[x][y]) {
vis[x][y] = true;//已访问
dfs(x, y);
vis[x][y] = false;//清除访问痕迹
if (flag) break;
}
}
}
这里记录一下递归函数怎么写,其实组成部分很简单,只需要考虑4点:
- 结束返回条件
- 访问前的判断
- 访问时的递归
- 访问后的操作
一般按照这个顺序,根据题意就可以写出递归函数。
3.检查函数
这个很简单,只需要提前设置变量flag记录是否有这样的一条路,然后dfs,最后根据flag结果返回真、假即可:
bool check(int x) {
flag = 0;//flag记录以该二分值x为最大伤害是否可以到达终点
memset(vis, 0, sizeof(vis));//初始化访问数组vis为0
dfs(1, 1);//从左上角(1,1)开始
if (flag)return true;
return false;
}
4.二分法的实现
二分法需要知道区间的左右范围(l和r),求这个区间的中间值mid = (l + r)/ 2,然后结合检查函数更新边界:
while (l + 1 < r) {
mid = (l + r) >> 1;//是取中间值的另一种写法
if (check(mid)) r = mid;//更新右边界
else l = mid;//更新左边界
}
代码解释:
- mid = (l + r) >> 1; 其中的“>>”是右移符,表示将二进制数右移一位并取整,相当于十进制数除二取整。eg:8的二进制数是1000,右移一位成100即4;7的二进制数是111,右移一位成11即3。
- 如果check返回真,代表以mid为某条路径的最大伤害值成立,即可以找到这样一条路(当然这条路的最大伤害值小于mid也成立),那么就需要枚举更小的mid优化,于是将右边界变为mid,在左边的区间找更小的值。反之亦然。
四、代码
讲到这里,整个题解就已经结束了,相信你已经明白该如何解决了,那么下面是这道题的代码,仅供参考:
#include<iostream>
using namespace std;
const int N = 1010;
int n, m, map[N][N], l, r, mid, x, y;//l和r是二分范围,x和y是地图坐标
int dx[4] = { -1,1,0,0 }, dy[4] = { 0,0,-1,1 };//x和y同时作用相当于左右下上
bool flag = 0, vis[N][N];//flag判断是否能到达终点 访问数组
//读取数值函数
int read() {
int s = 0, w = 1;
char ch = getchar();
//isdigit函数用来输入检测是否是0--9的数字,如果不是就检查是否是负号用w的正负来控制,如果是则读取数值
while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); }
while (isdigit(ch)) { s = s * 10 + ch - '0'; ch = getchar(); }
return s * w;
}
//深度递归dfs
void dfs(int xx, int yy) {
if (xx == n) { flag = 1; return; }
for (int i = 0; i < 4; i++) {
x = xx + dx[i]; y = yy + dy[i];//更新地图坐标
//如果范围在地图内 且 伤害值低于mid ,继续向四周递归
if (x >= 1 && x <= n && y >= 1 && y <= m && map[x][y] <= mid && !vis[x][y]) {
vis[x][y] = true;//已访问
dfs(x, y);
vis[x][y] = false;//清除访问痕迹
if (flag) break;
}
}
}
//检查函数
bool check(int x) {
flag = 0;//flag记录该二分值x是否可以到达终点
memset(vis, 0, sizeof(vis));
dfs(1, 1);
if (flag)return true;
return false;
}
int main(void) {
//读取数值的函数
n = read(), m = read();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
map[i][j] = read();
r = max(r, map[i][j]);//记录二分右边界
}
}
//计算
while (l + 1 < r) {
mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid;
}
//输出
cout << r;
return 0;
}
结束
本题思路及部分代码均来自洛谷用户@lzpclxf,笔记仅供大家参考和自我复习,如有侵权请联系删除(狗头保命)