UVA1601The Morning after Halloween 单向加双向bfs

题意:w*h(w,h\leqslant16)网格上有n(n\leqslant3)个小写字母(代表鬼)。要求把它们分别移动到对应的大写字母里。每步可以有多个鬼同时移动(均为往上下左右四个方向之一移动),但每步结束之后任何两个鬼不能占用同一个位置,也不能在一步之内交换位置。

先来第一种单向bfs,需要注意的是如果像平常bfs判断的话,一定会超时,因为每个节点每次有5种移动方式,3个节点,最多有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了

猜你喜欢

转载自blog.csdn.net/qq_36973725/article/details/84037553
今日推荐