希望我们不仅把编程当成一份工作,更要将其当成一份热爱!!!
-----潜意识中有个想成为一名厉害的程序员的梦
问题描述(137. 消息传输 (kamacoder.com))
在给定的 m x n (1 <= m, n <= 1000) 网格地图 grid
中,分布着一些信号塔,用于区域间通信。
每个单元格可以有以下三种状态:
-
值 0 代表空地,无法传递信号;
-
值 1 代表信号塔 A,在收到消息后,信号塔 A 可以在 1ms 后将信号发送给上下左右四个方向的信号塔;
-
值 2 代表信号塔 B,在收到消息后,信号塔 B 可以在 2ms 后将信号发送给上下左右四个方向的信号塔。
给定一个坐标 (j, k),输入保证坐标 (j, k) 位置一定有信号塔。在坐标 (j, k) 位置的信号塔触发一个信号。
要求返回网格地图中所有信号塔收到信号的最短时间,单位为 ms。如果有信号塔无法收到信号,则返回 -1。
输入描述
-
第一行:网格的列数
n
。 -
第二行:网格的行数
m
。 -
第三行:触发信号的信号塔坐标
(j, k)
。 -
接下来的
m
行:每行包含n
个整数,表示该行网格中每个位置的信号塔安装信息(通过空格间隔每个状态值)。
输出描述
输出返回网格地图中所有信号塔收到信号的最小时间,单位为 ms。如果不可能,返回 -1。
输入示例
3 3 1 0 0 1 2 1 2 1 0 1 2
输出示例
4
解题思路
广度搜索思想
按照时间为基准,求1s能到达的所有信号塔、然后递推2s能到达的所有信号塔、3s......,并记录到达每个信号塔的时间
-
时间基础变量:用于记录当前时间。
-
信号传播:先求1秒时能传递到的 所有信号塔 及其到达时间,再求2秒的,再求3秒的。这样求出的结果对每一个信号塔都是最短时间。
-
最大时间:其中所有信号塔中的最大时间,就是所有信号塔被传递信号的最小时间。
每一次循环中的操作
-
队列中的元素:代表当前时刻信号传递到的信号塔集合(例如:1s时传递到的信号塔们,2s时传递到的信号塔们)。
-
推测下一秒钟信号传递到的信号塔:
-
对于当前队列中的为1的信号塔,它下一秒就会向外辐射,我们对其进行辐射操作
-
对于当前队列中的为2的信号塔,判断 信号到达它的时间 是否是当前时间(时间基准)。
-
如果是,则不向外辐射(因为它要经过2s后才会向外辐射)
-
如果当前时间大于 它的到达时间1s,则向外辐射(因为它2s后辐射,当前已经过了1s,下一秒就会辐射)
-
-
-
处理完当前队列的信号塔后,也就推测出了下一秒到达的所有信号塔,然后:
-
时间基础变量加1。
-
-
当广度优先搜索完成后,查看每个信号塔的到达时间,如果其到达时间点为0(0是初始化的值),则代表它没有被辐射到,则输出-1:
代码(存在部分冗余,懒得改了)
#include<iostream>
#include<vector>
#include<list>
using namespace std;
int main()
{
vector<vector<int>> dir = { {1,0} ,{-1,0}, {0,1}, {0,-1} };
int n, m;
cin >> n;
cin >> m;
vector<vector<int>> grap(m, vector<int>(n, 0));//信号塔的值
vector<vector<bool>> visited(m, vector<bool>(n, false));//是否被信号传递到过
vector<vector<int>> times(m, vector<int>(n, 0));//每个信号塔被传递到的最小时间
int x, y;
cin >> x;
cin >> y;
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
{
cin >> grap[i][j];
}
list<pair<int, int>> my_que;//之所以用list作为队列,每次循环处理过程中:部分值为2的信号塔不会被处理,因此涉及到删除非队列头元素的问题,list更高效。
my_que.push_back({ x,y });
visited[x][y] = true;
int time_now = 0;
int max_time = 0;
while (!my_que.empty())
{
//每次循环的目的是求 下一秒能到达的所有信号塔,及其他们的到达时间
int size = my_que.size();
auto it = my_que.begin();
for (int j = 0; j < size; j++)
{
//对于当前队列中的为1的信号塔,它下一秒就会向外辐射,我们对其进行辐射操作
if (grap[it->first][it->second] == 1)
{
int x = it->first;
int y = it->second;
for (int i = 0; i < 4; i++)
{
int temp_x = x + dir[i][0];
int temp_y = y + dir[i][1];
//越界处理
if (temp_x < 0 || temp_x >= m || temp_y < 0 || temp_y >= n)
{
continue;
}
if (visited[temp_x][temp_y] == false && grap[temp_x][temp_y] != 0)//被辐射过的点不再辐射,空地不能辐射
{
visited[temp_x][temp_y] = true;//标记此信号塔被辐射过
times[temp_x][temp_y] = times[x][y] + grap[x][y];//求出此信号塔的最小到达时间
//求所有信号塔的到达时间的最大值
if (times[temp_x][temp_y] > max_time)
max_time = times[temp_x][temp_y];
my_que.push_back({ temp_x,temp_y });
}
}
it = my_que.erase(it);
}
//对于当前队列中的为2的信号塔,如果当前时间大于 它的到达时间1s,则向外辐射(因为它2s后辐射,当前已经过了1s,下一秒就会辐射)
else if (time_now - times[it->first][it->second] == 1)
{
int x = it->first;
int y = it->second;
for (int i = 0; i < 4; i++)
{
int temp_x = x + dir[i][0];
int temp_y = y + dir[i][1];
if (temp_x < 0 || temp_x >= m || temp_y < 0 || temp_y >= n)
{
continue;
}
if (visited[temp_x][temp_y] == false && grap[temp_x][temp_y] != 0)
{
visited[temp_x][temp_y] = true;
times[temp_x][temp_y] = times[x][y] + grap[x][y];
if (times[temp_x][temp_y] > max_time)
max_time = times[temp_x][temp_y];
my_que.push_back({ temp_x,temp_y });
}
}
it = my_que.erase(it);
}
//对于当前队列中的为2的信号塔,当前时间等于它的到达时间,则下一秒不会辐射。
else
{
it++;
}
}
time_now++;
}
//当广度优先搜索完成后,查看每个信号塔的到达时间,如果其到达时间点为0(0是初始化的值),则代表它没有被辐射到,则输出-1
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
{
if (i == x && j == y)
continue;
if (grap[i][j] != 0)
{
if (times[i][j] == 0)
{
cout << -1;
return 0;
}
}
}
cout << max_time;
return 0;
}
总结
通过这种方式,我们可以逐步推断出每一秒钟信号传播到的信号塔,最终求出所有信号塔的信号到达的最小时间,所有最小时间的最大值,即为所有信号被传到的最小时间