UVA1601The Morning after Halloween 單向加雙向bfs
阿新 • • 發佈:2018-11-19
題意: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了