牛客小白月赛100 D题 AMC中的AC题

题目描述

众所周知,出题人没玩过双人成行,所以出了这道题

你一觉醒来,发现你和另一个时空的你被困在 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^{_{2}}*m^{_{2}}),n和m最大为2000,总复杂度约O(1.6×10^{_{12}}),基本可以确定本题一定会卡着让你超时。

        于是我们要想到该如何优化,由于第一次的BFS是使动的,第二次的BFS是基于第一次的BFS而被动的(使动就是具有主动性,自己想出来的描述),因此第一次本体A到正确答案传送门的距离我们可以根据BFS遍历所有传送门去找,第二次的BFS我们可以通过预处理出 地图上所有点到各自最近传送门的距离 ,那么第一次的本体BFS通过找到传送门后,我们可以通过预处理得到的 镜像坐标B到其最近传送门的距离 线性地得到第二次BFS的距离,总复杂度 2 * O(n * m) 即 O(n * m),不会超时。

        另外有几个细节就是:

  1. 只要本体A能找到传送门,镜像B一定能找到传送门(原路返回然后再沿着A的路走一遍就行了)
  2. 镜像坐标也可以线性O(1) 地得到,X_{A}+X_{B} = 2X, Y_{A}+Y_{B} = 2Y

        最后到这思路也就很明确了,这道题还是值得一练的,需要考虑的点不少,最后代码如下:

/*
牛客小白赛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
*/

根据用户提供的关键词“小A 弹吉他 网 小白 108 比详情 参攻略”,以下是整合后的相关信息建议: --- ### 关于小白108的比详情 小白是由网主办的一系面向编程爱好者的在线竞之一。第108场事通常会围绕算法、数据结构以及实际问解决能力展开挑战。比目可能涉及但不限于字符串处理、动态规划、图论等领域。 对于与“小A弹吉他”相关的具体目,可能是某道以音乐或乐器为主的趣味性算法。这类目往往需要结合数学建模能力逻辑推理技巧来完成解答。 --- ### 如何准备此类比? #### 方法一:熟悉常见算法模板 确保掌握基础的数据结构(如栈、队)及经典算法模型(例如深度优先搜索DFS、广度优先搜索BFS)。针对可能出现的音符序匹配或者节奏计算等问提前复习KMP模式匹配法等相关知识点。 #### 方法二:模拟真实考场环境练习 利用过往的小白记录进训练,在规定时间内尝试独立解决问从而提升临场发挥水平。同时注意控制提交频率避免因超时错误而扣分过多。 #### 方法三:学习优秀选手思路分享 访问社区查看往届高排名玩家的经验贴。他们可能会提到如何快速理解复杂描述型试的方法论;也可能提供一些特别好用但容易被忽略掉的小技巧比如调试输出设置等细节优化方案。 --- ### 示例代码片段供参考(假设存在一个简单版本的问) 如果遇到类似判断两个旋律是否相同类型的程序设计,则可以考虑如下实现方式: ```python def is_same_melody(melody_a, melody_b): return melody_a == melody_b melody_A = list(map(int, input().split())) melody_B = list(map(int, input().split())) if len(melody_A) != len(melody_B): print("No") else: if is_same_melody(melody_A,melody_B): print("Yes") else: print("No") ``` 此段落仅为示意用途,请依据实际情况调整适应不同难度等级下的业务场景需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值