172. 立体推箱子
立体推箱子是一个风靡世界的小游戏。
游戏地图是一个N行M列的矩阵,每个位置可能是硬地(用”.”表示)、易碎地面(用”E”表示)、禁地(用”#”表示)、起点(用”X”表示)或终点(用”O”表示)。
你的任务是操作一个1×1×2的长方体。
这个长方体在地面上有两种放置形式,“立”在地面上(1×1的面接触地面)或者“躺”在地面上(1×2的面接触地面)。
在每一步操作中,可以按上下左右四个键之一。
按下按键之后,长方体向对应的方向沿着棱滚动90度。
任意时刻,长方体不能有任何部位接触禁地,并且不能立在易碎地面上。
字符”X”标识长方体的起始位置,地图上可能有一个”X”或者两个相邻的”X”。
地图上唯一的一个字符”O”标识目标位置。
求把长方体移动到目标位置(即立在”O”上)所需要的最少步数。
在移动过程中,”X”和”O”标识的位置都可以看作是硬地被利用。
输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包括两个整数N和M。
接下来N行用来描述地图,每行包括M个字符,每个字符表示一块地面的具体状态。
当输入用例N=0,M=0时,表示输入终止,且该用例无需考虑。
输出格式
每个用例输出一个整数表示所需的最少步数,如果无解则输出”Impossible”。
每个结果占一行。
数据范围
3≤N,M≤500
输入样例:
7 7
#######
#…X###
#…##O#
#…E#
#…E#
#…#
#######
0 0
输出样例:
10
/*
立着:0
横着躺:1
竖着躺:2
坐标(x,y)
500 * 500 * 3 宽搜
*/
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 510;
struct State
{
int x, y, lie;
};
int n,m;
char g[N][N];
int dist[N][N][3];//从起始状态到现在这个状态的距离
bool check(int x, int y) //判断是不是出界
{
if(x < 0 || x >= n || y < 0 || y >= m) return false;
return g[x][y] != '#';
}
int bfs(State start, State end)
{
memset(dist, -1, sizeof dist);
dist[start.x][start.y][start.lie] = 0;//起点
queue<State> q;
q.push(start);
int d[3][4][3] = {
{{-2, 0, 2}, {0, 1, 1}, {1, 0, 2}, {0, -2, 1}}, //0 立着 上右下左倒
{{-1, 0, 1}, {0, 2, 0}, {1, 0, 1}, {0, -1, 0}}, //1 横着躺
{{-1, 0, 0}, {0, 1, 2}, {2, 0, 0}, {0, -1, 2}}, //2 立着躺
};
while(q.size())
{
auto t = q.front();
q.pop();
//扩展t
for(int i = 0; i < 4; i++)
{
State next;
next = {t.x + d[t.lie][i][0], t.y + d[t.lie][i][1], d[t.lie][i][2]};
int x = next.x, y = next.y;
if(!check(x, y)) continue;//当前不合法 继续下一次循环
if(next.lie == 0 && g[x][y] == 'E') continue;
if(next.lie == 1 && !check(x, y + 1)) continue;
if(next.lie == 2 && !check(x + 1, y)) continue;
if(dist[x][y][next.lie] == -1)
{
dist[x][y][next.lie] = dist[t.x][t.y][t.lie] + 1;
q.push(next);
}
}
}
return dist[end.x][end.y][end.lie];
}
int main()
{
while(scanf("%d%d", &n, &m), n || m)
{
for(int i = 0; i < n; i ++) cin >> g[i];
State start = {-1}, end;
for(int i = 0; i < n; i ++)
for(int j = 0; j < m; j++)
if(g[i][j] == 'X' && start.x == -1)//第一次找到X 赋值起点
{
if(g[i][j + 1] == 'X') start = {i, j, 1}; //横着躺
else if(g[i + 1][j] == 'X') start = {i, j, 2};//竖着躺
else start = {i, j, 0};
}
else if(g[i][j] == 'O') end = {i, j, 0}; //这里是大o 终点
int res = bfs(start, end);
if(res == -1) puts("Impossible");
else printf("%d\n", res);
}
return 0;
}
173. 矩阵距离
给定一个N行M列的01矩阵A,A[i][j] 与 A[k][l] 之间的曼哈顿距离定义为:
dist(A[i][j],A[k][l])=|i−k|+|j−l|
输出一个N行M列的整数矩阵B,其中:
B[i][j]=min1≤x≤N,1≤y≤M,A[x][y]=1 dist(A[i][j],A[x][y])
输入格式
第一行两个整数n,m。
接下来一个N行M列的01矩阵,数字之间没有空格。
输出格式
一个N行M列的矩阵B,相邻两个整数之间用一个空格隔开。
数据范围
1≤N,M≤1000
输入样例:
3 4
0001
0011
0110
输出样例:
3 2 1 0
2 1 0 0
1 0 0 1
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
typedef pair<int, int>PII;
const int N = 1010;
int n, m; //表示矩阵的长和宽
char g[N][N]; //储存矩阵
int d[N][N]; //存储每个点到最近1的距离
void bfs()
{
memset(d, -1, sizeof d); //d初始化为-1
queue<PII> q;
for(int i = 0; i < n; i ++)
for(int j = 0; j < m; j++)
if(g[i][j] == '1')
{
q.push({i, j});
d[i][j] = 0; //1到1的距离为1
}
//遍历四个方向
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; //上右下左 顺时针
while (q.size())
{
auto t = q.front();
q.pop();
int x = t.first, y = t.second;
for(int i = 0; i < 4; i ++)
{
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < n && b >= 0 && b < m && d[a][b] == -1)
{
d[a][b] = d[x][y] + 1;
q.push({a, b});
}
}
}
}
int main()
{
scanf("%d%d", &n, &m); //读入长和宽
for(int i = 0; i < n; i ++) scanf("%s", g[i]); //读入矩阵
bfs();
for(int i = 0; i < n; i ++)
{
for(int j = 0; j < m; j++)
printf("%d ", d[i][j]);
puts("");
}
return 0;
}
174. 推箱子
推箱子游戏相信大家都不陌生,在本题中,你将控制一个人把1个箱子到目的地。
给定一张N行M列的地图,用字符”.”表示空地,字符”#”表示墙,字符”S”表示人的起始位置,字符”B”表示箱子的起始位置,字符”T”表示箱子的目标位置。
求一种移动方案,使箱子移动的次数最少,在此基础上再让人移动的总步数最少。
方案中使用大写的“EWSN”(东西南北)表示箱子的移动,使用小写的“ewsn”(东西南北)表示人的移动。
输入格式
输入包含多个测试用例。
对于每个测试用例,第一行包括两个整数N,M。
接下来N行,每行包括M个字符,用以描绘整个N行M列的地图。
当样例为N=0,M=0时,表示输入终止,且该样例无需处理。
输出格式
对于每个测试用例,第一行输出”Maze #”+测试用例的序号。
第二行输入一个字符串,表示推箱子的总体移动过程,若无解,则输出”Impossible.”。
每个测试用例输出结束后输出一个空行。
若有多条路线满足题目要求,则按照N、S、W、E的顺序优先选择箱子的移动方向(即先上下推,再左右推)。
在此前提下,再按照n、s、w、e的顺序优先选择人的移动方向(即先上下动,再左右动)。
数据范围
1≤N,M≤20
输入样例:
1 7
SB…T
1 7
SB…#.T
7 11
###########
#T##…#
#.#.#…####
#…B…#
#.######…#
#…S…#
###########
8 4
…
.##.
.#…
.#…
.#.B
.##S
…
###T
0 0
输出样例:
Maze #1
EEEEE
Maze #2
Impossible.
Maze #3
eennwwWWWWeeeeeesswwwwwwwnNN
Maze #4
swwwnnnnnneeesssSSS
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
typedef pair<int, int>PII;
const int N = 25;
struct Node
{
int x, y, dir;
};
int n, m; //矩阵长和宽
char g[N][N]; //表示整个地图
Node pre[N][N][4]; //记录上一步的状态
vector<int> path[N][N][4]; //存储人的路径
bool st[N][N][4], used[N][N]; //每个状态是否被遍历过 人的状态是不是被遍历过
PII dist[N][N][4]; //人的距离
int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1}; //上下左右方向推 往上推 去下,第一个为1
int pre_man[N][N]; //上一个人的状态
bool check(int x, int y) //判断是不是出了边界
{
return x >= 0 && x < n && y >= 0 && y < m && g[x][y] != '#';
}
int bfs_man(PII start, PII end, PII box, vector<int>&seq)
{
memset(used, false, sizeof used);
memset(pre_man, -1, sizeof pre_man);
queue<PII> q;
q.push(start);
used[start.first][start.second] = true;
used[box.first][box.second] = true;
while(q.size())
{
auto t = q.front();
q.pop();
if(t == end)
{
seq.clear();
int x = t.first, y = t.second;
while(pre_man[x][y] != -1)
{
int dir = pre_man[x][y] ^ 1;
seq.push_back(dir);
x += dx[dir], y += dy[dir];
}
return seq.size();
}
for(int i = 0; i < 4; i++) //枚举四个方向
{
int x = t.first, y = t.second;
int a = x + dx[i], b = y + dy[i];
if(check(a, b) && !used[a][b])
{
used[a][b] = true;
pre_man[a][b] = i;
q.push({a, b});
}
}
}
return -1;
}
bool bfs_box(PII man, PII box, Node &end)
{
memset(st, false, sizeof st); //状态初始化为false
//memset(pre, -1, sizeof pre); // 上一个状态初始化为-1
queue<Node>q;
for(int i = 0; i < 4; i++) //初始状态可以枚举的方向
{
int x = box.first, y = box.second; //箱子的位置
int a = x + dx[i], b = y + dy[i]; //要推箱子人所在的位置
int j = x - dx[i], k = y - dy[i]; //箱子推到的位置
vector<int> seq;
if(check(a,b) && check(j,k) && bfs_man(man, {a,b}, box, seq) != -1)
{
st[j][k][i] = true;
q.push({j, k, i});
dist[j][k][i] = {1, seq.size()}; //箱子移动的距离是1,人移动的距离是seq.size()
path[j][k][i] = seq;
pre[j][k][i] = {x, y, -1};
}
}
bool success = false;
PII man_d = {1e9, 1e9};
while(q.size())
{
auto t = q.front();
q.pop();
if(g[t.x][t.y] == 'T')
{
success = true;
if(dist[t.x][t.y][t.dir] < man_d)
{
man_d = dist[t.x][t.y][t.dir];
end = t;
}
}
for(int i = 0; i < 4; i++) //更新 枚举四个方向
{
int a = t.x + dx[i], b = t.y + dy[i]; //要推箱子人所在的位置
int j = t.x - dx[i], k = t.y - dy[i]; //箱子推到的位置
if(check(a, b) && check(j, k))
{
vector<int> seq; //人走的序列
auto &p = dist[j][k][i];
int distance = bfs_man({t.x + dx[t.dir], t.y + dy[t.dir]}, {a, b}, {t.x, t.y}, seq);
if(distance != -1) //可以走过去 更新状态
{
PII td = {dist[t.x][t.y][t.dir].first + 1, dist[t.x][t.y][t.dir].second + distance};//可以更新的距离
if(!st[j][k][i])
{
st[j][k][i] = true;
q.push({j, k, i});
path[j][k][i] = seq;
pre[j][k][i] = t;
p = td;
}
else if(p > td)
{
p = td;
path[j][k][i] = seq;
pre[j][k][i] = t;
}
}
}
}
}
return success;
}
int main()
{
int T = 1;
while(cin >> n >> m, n || m)
{
for(int i = 0; i < n; i ++) cin >> g[i];
printf("Maze #%d\n", T++);
PII man, box;//找到人和箱子的起始位置
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
if(g[i][j] == 'S') man = {i, j};
else if(g[i][j] == 'B') box = {i, j};
Node end; //终点状态
if(!bfs_box(man, box, end)) puts("Impossible.");
else
{
char ops[] = "nswe";
string res;
while(!(end.x == box.first && end.y == box.second)) //不等于起点
{
res += ops[end.dir] - 32;
for(auto dir : path[end.x][end.y][end.dir]) res += ops[dir];
end = pre[end.x][end.y][end.dir];
}
reverse(res.begin(), res.end());
cout << res << endl;
}
puts("");
}
return 0;
}