算法分析
bfs
思路:大胖子走迷宫的过程中,会随着时间的推移,体形会变小,有些位置可能现在太胖走不过去,卡住了,可能在原地等着消耗,或者走其他路就能穿过去,因此走到某个位置时,还需要记录走到当前位置对应的时间(即可推出体型大小),与普通的bfs
不同的是,普通bfs
是二维的,而这题的bfs
是三维的,还有时间的存在
操作:
- 1、用一个结构体存储当前走到的位置和对应的时间
{x, y, time}
- 2、在起点开始出发,进行
bfs
,在当前状态可以延伸5
个状态- (1):往上走,对应
(x - 1, y, time + 1)
- (2):往右走,对应
(x, y + 1, time + 1)
- (3):往下走,对应
(x + 1, y, time + 1)
- (4):往左走,对应
(x, y - 1, time + 1)
- (5):不走,自动消耗
(x, y, time + 1)
- (1):往上走,对应
- 3、走下一个状态的过程中,还需要根据体型判断有没有撞到障碍物(一定要根据当前位置的体型去判断,不能根据走到了下一个位置的状态的体型去判断)
- 4、需要用
st[][]
数组记录哪些位置已经走过,走过的位置不再走,因为不存在 从原地走去别的地方又走回来 比 直接待在原地自动消耗 的情况更优
时间复杂度 $O(n^2)$
参考文献
蓝桥杯
Java 代码
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class Main {
static int N = 310;
static char[][] g = new char[N][N];
static boolean[][] st = new boolean[N][N];
static int[] dx = new int[] {0, -1, 0, 1};
static int[] dy = new int[] {-1, 0, 1, 0};
//有3种形态,例如长度是5 * 5,则中心是(x, y),
对应的区域是左上角(x - 2, y - 2),右上角(x + 2, y + 2),左下角(x - 2, y + 2),右下角(x + 2, y + 2)
static int[] d = new int[] {2, 1, 0};
static int n, k;
static int bfs()
{
Queue<Edge> q = new LinkedList<Edge>();
q.add(new Edge(3, 3, 0));
st[3][3] = true;
while(!q.isEmpty())
{
Edge t = q.poll();
//将源点加入到队列
q.add(new Edge(t.x, t.y, t.time + 1));
//枚举4个方向
for(int i = 0;i < 4;i ++)
{
int a = t.x + dx[i], b = t.y + dy[i];
int fat = t.time / k >= 2 ? 0 : d[t.time / k];//判断有多胖
if(st[a][b]) continue;
if(a - fat < 1 || a + fat > n || b - fat < 1 || b + fat > n)
continue;
//判断该区域是否有障碍物
boolean flag = false;//初始没有障碍物
for(int u = a - fat;u <= a + fat;u ++)
for(int v = b - fat;v <= b + fat;v ++)
if(g[u][v] == '*')
flag = true;
if(flag) continue;
if(a == n - 2 && b == n - 2) return t.time + 1;//到达终点
st[a][b] = true;
//加入到队列
q.add(new Edge(a, b, t.time + 1));
}
}
return -1;
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
n = scan.nextInt();
k = scan.nextInt();
for(int i = 1;i <= n;i ++)
{
char[] temp = scan.next().toCharArray();
for(int j = 1;j <= n;j ++)
{
g[i][j] = temp[j - 1];
}
}
System.out.println(bfs());
}
}
class Edge
{
int x, y, time;
Edge(int x, int y, int time)
{
this.x = x;
this.y = y;
this.time = time;
}
}
#include[HTML_REMOVED]
#include[HTML_REMOVED]
#include[HTML_REMOVED]
using namespace std;
#define N 310
struct edge{
int x;
int y;
int t;
edge(int x, int y, int t): x(x), y(y), t(t){};
};
static char grid[N][N];
static bool st[N][N];
static int dx[4] = {0, -1, 0, 1};
static int dy[4] = {-1, 0, 1, 0};
static int d[3] = {2, 1, 0};
static int n, k;
int bfs(){
queue[HTML_REMOVED] q;
q.push(new edge(3, 3, 0));
st[3][3] = true;
}
int main(){
cin >> n >> k;
for(int i = 1; i <= n; i){
for(int j = 1; j <= n; j){
char c;
scanf(“%c”, &c);
grid[i][j] = c;
printf(“%c”, grid[i][j]);
}
}
printf(“%d”, bfs());
return 0;
}
为什么我跟你写的一模一样 只是换成用C++写的 为什么题目给的案例运行结果是21 而不是16 我一个个字母对着你的检查了一遍 看不出哪里不一样啊 折磨 呆佬可以帮我看看吗
你这每次都出队列一个,却又至少入队列一个(源点但时间+1了),如果到达不来终点,为什么不会死循环
最坏就是全部一遍,发现没到终点,st保证不走重复路
我想问的是,如果没有终点,while里面的return永远不会执行,每次队列的大小至少唯一,不会死循环吗
如果题目没有解,就死循环了,这题题目保证有解,所以不会死循环
是不是因为无论他走还是不走,时间都是变化的
如果一直是走动的的为什么一开始要加1呢????
q.add(new Edge(t.x, t.y, t.time + 1));
//将源点加入到队列
q.add(new Edge(t.x, t.y, t.time + 1));
这个地方可以有一个优化,如果时间让体积已经减到最小了,那么原地停留没有意义了。时间优化了三倍
为什么一定要根据当前的体型去判断呢 走到下一位置不应该是去按照下一位置的体型去判断吗
从当前位置走到下一个位置是有一个过程的,整个过程都需要满足不被障碍物卡住才能走过去,可以理解成在走到下一个位置时,体积慢慢减少,而不是瞬间减少,因此只能用当前的体积去走下一个位置
啊 懂了 感谢大佬
将原点加入队列换成这样写为什么不对
完整代码
因为在枚举原点时,在
for (int i = 0; i < 4; i++)
这段前面你已经把在源点不动的状态,加入到队列中,后面枚举4
个方向时,若存在障碍物,又把源点不动的状态加入到队列中,会导致存在重复遍历的点,其实这一步是没必要的。因此数据范围较大时就会超时q.add(new Edge(t.x, t.y, t.time + 1)); 大佬 为什么这行代码 写成 dx[4]=0 dy[4]=0; 是不对的呢
如果给dx, dy多加一个0, 0的偏移量, 这一句代码是一定不会被执行的, 因为会被st[a][b]continue掉原点
对
哇 知道了 感谢
nb
大佬,复杂度是怎么计算的
每个位置最多只走一次,最多走$n^2$个位置,最多在某个位置停留$2k$次,因此时间复杂度是$O(n^2 + 2k)$
修改下,停留的次数最多是$2k$
我跟你基本一样 但是我用BufferedReader输入流超时了 用Scanner居然不超时 啥情况?
代码
Queue[HTML_REMOVED]q=new LinkedList[HTML_REMOVED]();
好吧 现在Buffered提交居然可以过了 可能评测系统原因?
完整代码.....理论上来说是不会超时的
在代码里面加三个`,把代码框出来
不知道
大佬,您代码里只有上下左右四种情况,没有体现原地不动消耗脂肪的情况呀?
将原点加入到队列 注释的 下一行就是了
tql,呆佬