题意:w*h(w,h16)网格上有n(n3)个小写字母(代表鬼)。要求把它们分别移动到对应的大写字母里。每步可以有多个鬼同时移动(均为往上下左右四个方向之一移动),但每步结束之后任何两个鬼不能占用同一个位置,也不能在一步之内交换位置。
先来第一种单向bfs,需要注意的是如果像平常bfs判断的话,一定会超时,因为每个节点每次有5种移动方式,3个节点,最多有种情况,但是可以将空地拿出来,变成图。题目中有一个非常好用的条件——任何一个2*2子网格中至少有一个障碍格,最多16*16个格子,每四个有一个障碍物,也就是256*(3/4)=192,所以空地有最多192个,判重的话就用200*200*200=8000000数组来存,另外图是将二维坐标映射成一维数组。开始时候每次找到一个空地那么空地数量加一,也就是坐标映射值。一开始竟然没有想到。。
#include <cstdio>
#include <cstring>
#include <cctype>
#include <list>
#include <algorithm>
using namespace std;
int w, h, n;
char pic[20][20]; // 输入
int num[20][20]; // 输入中的位置→图中节点的编号
int vis[200][200][200]; // 标记数组
int connect[200][200]; // 邻接表
int all; // 图中节点的数量
int que[10000000][4]; // BFS队列
int goal[4]; // 目标状态
inline void BFS() {
// 初始化
memset(vis, 0, sizeof(vis));
int fro = 0, rear = 1;
vis[que[0][1]][que[0][2]][que[0][3]] = true;
while(fro < rear) {
int &step = que[fro][0], &a = que[fro][1], &b = que[fro][2], &c = que[fro][3];
if(a == goal[1] && b == goal[2] && c == goal[3]) { goal[0] = step; return; }
for(int i = 0, t1; i <= connect[a][0]; ++i) {
t1 = (i == 0 ? a : connect[a][i]);
for(int j = 0, t2; j <= connect[b][0]; ++j) {
t2 = (j == 0 ? b : connect[b][j]);
for(int k = 0, t3; k <= connect[c][0]; ++k) {
t3 = (k == 0 ? c : connect[c][k]);
// 判断冲突-----
if((t1 && t2 && t1 == t2) || (t1 && t3 && t1 == t3) || (t2 && t3 && t2 == t3)) continue; // 不能重合
if(t1 && t2 && t1 == b && t2 == a) continue; // t1,t2不能对穿
if(t1 && t3 && t1 == c && t3 == a) continue; // t1,t3不能对穿
if(t2 && t3 && t2 == c && t3 == b) continue; // t2,t3不能对穿
// ----------
if(!vis[t1][t2][t3]) {
vis[t1][t2][t3] = 1;
que[rear][0] = step + 1, que[rear][1] = t1, que[rear][2] = t2, que[rear][3] = t3;
++rear;
}
}
}
}
++fro;
}
}
int main() {
int _t = 0;
while(scanf("%d%d%d\n", &w, &h, &n) && w && h && n) {
// 读取输入-----
for(int i = 0; i != h; ++i) fgets(pic[i],20,stdin);
// ----------
// 根据输入建立图-----
// 初始化
memset(connect, 0, sizeof(connect));
all = 0;
// 获得编号
for(int i = 0; i != h; ++i) for(int j = 0; j != w; ++j) {
if(pic[i][j] != '#') num[i][j] = ++all;
else num[i][j] = 0;
}
// 建立图
for(int i = 0; i != h; ++i) for(int j = 0; j != w; ++j) if(num[i][j]) {
int &pos = num[i][j];
if(num[i + 1][j]) connect[pos][++connect[pos][0]] = num[i + 1][j];
if(num[i - 1][j]) connect[pos][++connect[pos][0]] = num[i - 1][j];
if(num[i][j + 1]) connect[pos][++connect[pos][0]] = num[i][j + 1];
if(num[i][j - 1]) connect[pos][++connect[pos][0]] = num[i][j - 1];
}
// ----------
// 寻找初始状态和目标状态(测了一下字母范围只在abc之间所以偷懒就这么写了)
//初始化
que[0][0] = que[0][1] = que[0][2] = que[0][3] = 0;
goal[0] = goal[1] = goal[2] = goal[3] = 0;
// 寻找初始状态
for(int i = 0; i != h; ++i) for(int j = 0; j != w; ++j) if(islower(pic[i][j])) {
if(pic[i][j] == 'a') que[0][1] = num[i][j];
if(pic[i][j] == 'b') que[0][2] = num[i][j];
if(pic[i][j] == 'c') que[0][3] = num[i][j];
}
// 寻找目标状态
for(int i = 0; i != h; ++i) for(int j = 0; j != w; ++j) if(isupper(pic[i][j])) {
if(pic[i][j] == 'A') goal[1] = num[i][j];
if(pic[i][j] == 'B') goal[2] = num[i][j];
if(pic[i][j] == 'C') goal[3] = num[i][j];
}
// ----------
BFS();
printf("%d\n", goal[0]);
}
}
正常思路是这个但是超时,下面看一下作者写的单向bfs,又是二进制。。
区别就是他使用了编码队列
#include<cstdio>
#include<cstring>
#include<cctype>
#include<queue>
using namespace std;
const int maxs = 20;
const int maxn = 150; // 75% cells plus 2 fake nodes
const int dx[]={1,-1,0,0,0}; // 4 moves, plus "no move"
const int dy[]={0,0,1,-1,0};
inline int ID(int a, int b, int c) {
return (a<<16)|(b<<8)|c;
}
int s[3], t[3]; // starting/ending position of each ghost
int deg[maxn], G[maxn][5]; // target cells for each move (including "no move")
inline bool conflict(int a, int b, int a2, int b2) {
return a2 == b2 || (a2 == b && b2 == a);
}
int d[maxn][maxn][maxn]; // distance from starting state
int bfs() {
queue<int> q;
memset(d, -1, sizeof(d));
q.push(ID(s[0], s[1], s[2])); // starting node
d[s[0]][s[1]][s[2]] = 0;
while(!q.empty()) {
int u = q.front(); q.pop();
int a = (u>>16)&0xff, b = (u>>8)&0xff, c = u&0xff;
if(a == t[0] && b == t[1] && c == t[2]) return d[a][b][c]; // solution found
for(int i = 0; i < deg[a]; i++) {
int a2 = G[a][i];
for(int j = 0; j < deg[b]; j++) {
int b2 = G[b][j];
if(conflict(a, b, a2, b2)) continue;
for(int k = 0; k < deg[c]; k++) {
int c2 = G[c][k];
if(conflict(a, c, a2, c2)) continue;
if(conflict(b, c, b2, c2)) continue;
if(d[a2][b2][c2] != -1) continue;
d[a2][b2][c2] = d[a][b][c]+1;
q.push(ID(a2, b2, c2));
}
}
}
}
return -1;
}
int main() {
int w, h, n;
while(scanf("%d%d%d\n", &w, &h, &n) == 3 && n) {
char maze[20][20];
for(int i = 0; i < h; i++)
fgets(maze[i], 20, stdin);
// extract empty cells
int cnt, x[maxn], y[maxn], id[maxs][maxs]; // cnt is the number of empty cells
cnt = 0;
for(int i = 0; i < h; i++)
for(int j = 0; j < w; j++)
if(maze[i][j] != '#') {
x[cnt] = i; y[cnt] = j; id[i][j] = cnt;
if(islower(maze[i][j])) s[maze[i][j] - 'a'] = cnt;
else if(isupper(maze[i][j])) t[maze[i][j] - 'A'] = cnt;
cnt++;
}
// build a graph of empty cells
for(int i = 0; i < cnt; i++) {
deg[i] = 0;
for(int dir = 0; dir < 5; dir++) {
int nx = x[i]+dx[dir], ny = y[i]+dy[dir];
// "Outermost cells of a map are walls" means we don't need to check out-of-bound
if(maze[nx][ny] != '#') G[i][deg[i]++] = id[nx][ny];
}
}
// add fakes nodes so that in each case we have 3 ghosts. this makes the code shorter
if(n <= 2) { deg[cnt] = 1; G[cnt][0] = cnt; s[2] = t[2] = cnt++; }
if(n <= 1) { deg[cnt] = 1; G[cnt][0] = cnt; s[1] = t[1] = cnt++; }
printf("%d\n", bfs());
}
return 0;
}
双向bfs:
就是终点和起点一起开始,看谁先到达对方经过的点。具体实现看代码。
#include<cstdio>
#include<cstring>
#include<cctype>
#include<queue>
using namespace std;
const int maxs = 20;
const int maxn = 150; // 75% cells plus 2 fake nodes
const int dx[] = { 1,-1,0,0,0 }; // 4 moves, plus "no move"
const int dy[] = { 0,0,1,-1,0 };
inline int ID(int a, int b, int c) {
return (a << 16) | (b << 8) | c;
}
int s[3], t[3]; // starting/ending position of each ghost
int deg[maxn], G[maxn][5]; // target cells for each move (including "no move")
inline bool conflict(int a, int b, int a2, int b2) {
return (a2 == b2) || (a2 == b && b2 == a);
}
int d[maxn][maxn][maxn]; // distance from starting state
int vis[maxn][maxn][maxn];
int bfs() {
queue<int> q;
queue<int>p;
memset(d, 0, sizeof(d));
memset(vis, 0, sizeof(vis));
q.push(ID(s[0], s[1], s[2])); // starting node
p.push(ID(t[0], t[1],t[2]));
d[s[0]][s[1]][s[2]] = 0;
d[t[0]][t[1]][t[2]] = 1;
vis[s[0]][s[1]][s[2]] = 1;//将从起点经过的标记为1
vis[t[0]][t[1]][t[2]] = 2;//从终点开始的标记为2
while (!q.empty()||!q.empty()) {
int c1 = q.size(); int c2 = p.size();
while (c1--) {
int u = q.front(); q.pop();
int a = (u >> 16) & 0xff, b = (u >> 8) & 0xff, c = u & 0xff;
for (int i = 0; i < deg[a]; i++) {
int a2 = G[a][i];
for (int j = 0; j < deg[b]; j++) {
int b2 = G[b][j];
if (conflict(a, b, a2, b2)) continue;
for (int k = 0; k < deg[c]; k++) {
int c2 = G[c][k];
if (conflict(a, c, a2, c2)) continue;
if (conflict(b, c, b2, c2)) continue;
if (!vis[a2][b2][c2]) {
vis[a2][b2][c2] = 1;//
d[a2][b2][c2] = d[a][b][c] + 1;
q.push(ID(a2, b2, c2));
}
else if (vis[a2][b2][c2] == 2) {//走到终点经过的节点
return d[a][b][c] + d[a2][b2][c2];//返回起点走过的路程加上从终点走过的路程
}
}
}
}
}
while (c2--) {
int u = p.front(); p.pop();
int a = (u >> 16) & 0xff, b = (u >> 8) & 0xff, c = u & 0xff;
for (int i = 0; i < deg[a]; i++) {
int a2 = G[a][i];
for (int j = 0; j < deg[b]; j++) {
int b2 = G[b][j];
if (conflict(a, b, a2, b2)) continue;
for (int k = 0; k < deg[c]; k++) {
int c2 = G[c][k];
if (conflict(a, c, a2, c2)) continue;
if (conflict(b, c, b2, c2)) continue;
if (!vis[a2][b2][c2]) {
vis[a2][b2][c2] = 2;//
d[a2][b2][c2] = d[a][b][c] + 1;
p.push(ID(a2, b2, c2));
}
else if (vis[a2][b2][c2] == 1) {//走到起点经过的节点
return d[a][b][c] + d[a2][b2][c2];//返回起点走过的路程加上从终点走过的路程
}
}
}
}
}
}
return -1;
}
int main() {
int w, h, n;
while (scanf("%d%d%d\n", &w, &h, &n) == 3 && n) {
char maze[20][20];
for (int i = 0; i < h; i++)
fgets(maze[i], 20, stdin);
// extract empty cells
int cnt, x[maxn], y[maxn], id[maxs][maxs]; // cnt is the number of empty cells
cnt = 0;
for (int i = 0; i < h; i++)
for (int j = 0; j < w; j++)
if (maze[i][j] != '#') {
x[cnt] = i; y[cnt] = j; id[i][j] = cnt;
if (islower(maze[i][j])) s[maze[i][j] - 'a'] = cnt;
else if (isupper(maze[i][j])) t[maze[i][j] - 'A'] = cnt;
cnt++;
}
// build a graph of empty cells
for (int i = 0; i < cnt; i++) {
deg[i] = 0;
for (int dir = 0; dir < 5; dir++) {
int nx = x[i] + dx[dir], ny = y[i] + dy[dir];
// "Outermost cells of a map are walls" means we don't need to check out-of-bound
if (maze[nx][ny] != '#') G[i][deg[i]++] = id[nx][ny];
}
}
// add fakes nodes so that in each case we have 3 ghosts. this makes the code shorter
if (n <= 2) { deg[cnt] = 1; G[cnt][0] = cnt; s[2] = t[2] = cnt++; }
if (n <= 1) { deg[cnt] = 1; G[cnt][0] = cnt; s[1] = t[1] = cnt++; }
printf("%d\n", bfs());
}
return 0;
}
时间从850ms变成470ms了