题目描述
众所周知,出题人没玩过双人成行,所以出了这道题
你一觉醒来,发现你和另一个时空的你被困在 n∗m 大小矩形孤岛的 (x,y)地块上
在地图中最多包含 平地,陷阱和传送门 三种不同地块
你和另外一个时空的你都可以上下左右移动到相邻的地块中
可是你和外一个时空的你只能同时以相反的方向移动
两人均不能跨过边界,即到达孤岛外的地方;任意人到达陷阱处会立刻死亡
现在,你能否给出一个移动序列,使得两人均能从传送门离开,其中任意一人到达传送门后一定会离开且不会再回到该孤岛中;
如果有,请输出该序列的最短长度、反之输出 -1
输入描述:
第一行四个正整数 n,m,x,y
接下来 n 行,每行一个长度为 m 的字符串
1≤n,m≤2×103; 1≤x≤n; 1≤y≤m
数据保证
字符串仅包含 .#@ 三种字符 .(平地) #(陷阱) @(传送门)
保证 (x,y) 位置是平地.
输出描述:
输出一个整数
若能离开,请请输出该序列的最短长度
反之输出 -1
示例1
输入
3 3 2 2 @.@ #.. @.@
输出
2
说明
你可以先往上后往左到达(1,1)传送门
另外一个时空的你会先下后右到达(3,3)传送门
示例2
输入
1 3 1 2 ..@
输出
3
示例3
输入
3 1 2 1 # . @
输出
-1
说明
显然,谁都不想走到陷阱那 ...
思路分析:
这道题一眼看上去就知道是BFS的01最短路径问题,但是多了个镜像点也需要到达传送门的限制。找本体和镜像(统称本体为A,镜像为B)我的第一反应是贪心,也就是本体找到离自己的最近的传送门后,再bfs镜像让镜像找到离自己最近的传送门,但仔细一想后很显然这是错误的:答案由A.Length和B.Length组成,且A.Length一定大于B.Length(因为贪心的话本体一定是先找最近的,镜像一定找偏远的),那么存在这么一种情况:case1的A.Length为5,B.Length为3,ans=8,case2的A.Length为6,B.Length为0,ans=6,很明显case2比case1要近,显然贪心是错的。很快考虑到贪心是错的之后,就该意识到答案一定是由A.Length和B.Length二者综合组成。
如果通过BFS暴搜A点到所有的传送门距离,再基于A点找到传送门时的镜像坐标B再次进行暴力搜索,那么显然是可以可以得到答案的,但是第一次暴搜时间复杂度O(n*m),第二次BFS要找到最近的传送门,最坏情况仍然是O(n*m),两次暴搜实践复杂度O(),n和m最大为2000,总复杂度约O(1.6×
),基本可以确定本题一定会卡着让你超时。
于是我们要想到该如何优化,由于第一次的BFS是使动的,第二次的BFS是基于第一次的BFS而被动的(使动就是具有主动性,自己想出来的描述),因此第一次本体A到正确答案传送门的距离我们可以根据BFS遍历所有传送门去找,第二次的BFS我们可以通过预处理出 地图上所有点到各自最近传送门的距离 ,那么第一次的本体BFS通过找到传送门后,我们可以通过预处理得到的 镜像坐标B到其最近传送门的距离 线性地得到第二次BFS的距离,总复杂度 2 * O(n * m) 即 O(n * m),不会超时。
另外有几个细节就是:
- 只要本体A能找到传送门,镜像B一定能找到传送门(原路返回然后再沿着A的路走一遍就行了)
- 镜像坐标也可以线性O(1) 地得到,
最后到这思路也就很明确了,这道题还是值得一练的,需要考虑的点不少,最后代码如下:
/*
牛客小白赛100 ACM中的AC题
https://ac.nowcoder.com/acm/contest/88878/D
*/
#include <bits/stdc++.h>
using namespace std;
int dx[4] = {1,-1,0,0},dy[4] = {0,0,1,-1};
char mp[2005][2005];
int vis[2005][2005],dis[2005][2005]; //vis[]记录本体bfs的距离,dis[]为预处理的距离
int n,m,x,y;
deque< pair<int,int> >dq;
bool check(int x,int y)
{
if(mp[x][y] != '#' && x >= 1 && x <= n && y >= 1 && y <= m) return true;
return false;
}
void init() //预处理所每个点和最近传送门的距离
{
while(!dq.empty())
{
auto node = dq.front(); //当前点
dq.pop_front();
for(int i = 0; i < 4; i++)
{
int nx = node.first + dx[i],ny = node.second + dy[i];
if(check(nx,ny) && !vis[nx][ny])
{
vis[nx][ny] = 1;
dis[nx][ny] = dis[node.first][node.second] + 1;
dq.push_back({nx,ny});
}
}
}
}
int bfs() //ans = 本体到某一传送门 + 镜像位置到另一传送门,枚举所有传送门
{
init();
memset(vis,0,sizeof(vis)); //置0
int ans = INT_MAX;
vis[x][y] = 1;
dq.push_back({x,y});
while(!dq.empty())
{
pair<int,int> node = dq.front();
dq.pop_front();
int xx = 2 * x - node.first,yy = 2 * y - node.second; //镜像坐标
if(mp[node.first][node.second] == '@') ans = min(ans,vis[node.first][node.second] - 1 + dis[xx][yy]);
for(int i = 0; i < 4; i++)
{
int nx = node.first + dx[i],ny = node.second + dy[i];
int nxx = 2 * x - nx,nyy = 2 * y - ny; //下一步的镜像坐标
if(check(nx,ny) && check(nxx,nyy) && !vis[nx][ny]) //检查下一步的本体和镜像
{
vis[nx][ny] = vis[node.first][node.second] + 1;
dq.push_back({nx,ny});
}
}
}
if(ans != INT_MAX) return ans;
return -1;
}
int main()
{
cin>>n>>m>>x>>y;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
cin>>mp[i][j];
if(mp[i][j] == '@') {dq.push_back({i,j}); vis[i][j] = 1;}
}
cout<<bfs();
return 0;
}
/*
3 3 2 2
@.@
#..
@.@
*/
/*
2
*/
571

被折叠的 条评论
为什么被折叠?



