刺杀大使 题解
题目描述
某组织正在策划一起对某大使的刺杀行动。他们来到了使馆,准备完成此次刺杀,要进入使馆首先必须通过使馆前的防御迷阵。
迷阵由n×m个相同的小房间组成,每个房间与相邻四个房间之间有门可通行。在第n行的m个房间里有 m 个机关,这些机关必须全部打开才可以进入大使馆。而第 1 行的 m 个房间有 m 扇向外打开的门,是迷阵的入口。除了第 1 行和第 n 行的房间外,每个房间都被使馆的安保人员安装了激光杀伤装置,将会对进入房间的人造成一定的伤害。第 i 行第 j 列 造成的伤害值为 pi,j(第 1 行和第 n 行的 p 值全部为 0)。
现在某组织打算以最小伤害代价进入迷阵,打开全部机关,显然,他们可以选择任意多的人从任意的门进入,但必须到达第 n 行的每个房间。一个士兵受到的伤害值为他到达某个机关的路径上所有房间的伤害值中的最大值,整个部队受到的伤害值为所有士兵的伤害值中的最大值。现在,这个恐怖组织掌握了迷阵的情况,他们需要提前知道怎么安排士兵的行进路线可以使得整个部队的伤害值最小。
输入格式
第一行有两个整数 n,m,表示迷阵的大小。
接下来 n 行,每行 m 个数,第 i 行第 j 列的数表示 pi,j。
输出格式
输出一个数,表示最小伤害代价。
输入输出样例
输入 #1
4 2
0 0
3 5
2 4
0 0
输出 #1
3
说明/提示
50% 的数据,n,m≤100;
100% 的数据,n,m≤1000,pi,j≤1000。
解题思路
本体最容易想到的思路其实就是题目标签里的二分和BFS。
题目虽然说必须到达第 n 行的每个房间,但其实最后一行的房间之间也都是连通的,因此只要到达第n行任意一个房间即可。
一条线路的伤害值是这一条路线上一路走来所受到的最大伤害值,而我们现在要求所有可能的路线的伤害值中的最小值,即求最大值的最小值。将所有路线全部走一遍然后求其中最小的伤害值显然是不现实的,因此我们可以先确定最小值是多少,再判断伤害值是那个最小值的路线存不存在。
具体做法就是先用二分法确定中间的伤害值是多少(可以先将其设为a),再从(1,1)开始用BFS,判断是否有伤害值为a的路线存在,
如果有,说明可能还能更小,将原来右边界的值更新为a-1,再用一次二分法,求出新的中间值,再次判断是否有路线存在,以此类推,直到左边界<=右边界
如果没有,就将原来左边界的值更新为a+1,再用一次二分法,求出新的中间值,再次判断是否有路线存在,以此类推,直到左边界<=右边界
BFS的部分主要加一个判断新点的伤害值是否大于a,如果是,就跳过,不是,就将新点入队,以此确保a(中间伤害值)是这条线路的最大伤害值
AC Code
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int arr[1005][1005];//用来存每个房间的伤害值
int flag[1005][1005] = { 0 };//用来判断每个房间是否被走过
int sq[1000000][2];//表示队列的数组
int xy[4][2] = { {0,1},{0,-1},{1,0},{-1,0} };//上下左右四个方向
int n, m;
int l = 1e8;//100000000
int r = 1e-8;// 0.00000001
int mid,ans;
bool bfs(int midd)
{
int head = 0;//相当于队头指针,指示队头位置
int tail = 1;//相当于队尾指针,指示队尾位置
int xx;//用来存新点横坐标
int yy;//用来存新点纵坐标
while (head < tail)//只要队不为空
{
head++;//队列头元素出队,队头位置加一
int x = sq[head][0];//取队头元素横坐标
int y = sq[head][1];//取队头元素纵坐标
flag[x][y] = 1;//标记队头的点(元素)走过了
for (int i = 0; i < 4; i++)//4个方向遍历
{
xx = x + xy[i][0];
yy = y + xy[i][1];
if (xx<1 || yy<1 || xx>n || yy>m)//超边界
{
continue;//跳过
}
if (flag[xx][yy] == 1)//走过
{
continue;//跳过
}
if (arr[xx][yy] > midd)//伤害过大
{
continue;//跳过
}
flag[xx][yy] = 1;//标记走过
if (xx == n)//到达最后一行
return 1;
else
{
tail++;//新点入队,队尾位置加一
sq[tail][0] = xx;//新点横坐标入队
sq[tail][1] = yy;//新点纵坐标入队
}
}
}
return 0;//找不到,返回0
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> arr[i][j];
r = max(r, arr[i][j]);//找最大伤害值,作为右边界
l = min(l, arr[i][j]);//找最小伤害值,作为左边界
}
}
sq[1][0] = 1;
sq[1][1] = 1;
//将点(1,1)入队,表示从点(1,1)开始走
flag[1][1] = 1;
while (l <= r) { //二分法
mid = (l + r) / 2;
memset(flag, 0, sizeof(flag)); //重置标记数组
if (bfs(mid)) r = mid - 1, ans = mid; //如果这个mid可行,说明可能还能再小,于是更新答案 + 缩小范围
else l = mid + 1; //如果这个mid不可行,说明不可能再小,也缩小范围,不更新答案
}
printf("%d", ans);
return 0;
}