当前位置: 代码迷 >> 综合 >> UVa 1601 The morning after Halloween(单向BFS+双向BFS)
  详细解决方案

UVa 1601 The morning after Halloween(单向BFS+双向BFS)

热度:71   发布时间:2023-09-23 09:42:29.0
//测试数据 
/*
5 5 2
#####
#A#B#
#   #
#b#a#
#####
16 4 3
################
## ########## ##
#    ABCcba    #
################
16 16 3
################
### ##    #   ##
##  #  ##   # c#
#  ## ########b#
# ##  # #   #  #
#  # ##   # # ##
##  a#  # # #  #
### ## #### ## #
##   #   #  #  #
#  ##### # ## ##
####   #B# #   #
##  C#   #   ###
#  # # ####### #
# ######  A##  #
#        #    ##
################
0 0 0
*/#include<cstdio>
#include<cstring>
#include<cctype>
#include<queue>
using namespace std;
const int maxs=20;
const int maxn=200;int s[3],t[3];
int deg[maxn],G[maxn][5];  //G[][]是邻接表保存,deg[i]保存第i个点有几个相连点 
int dis[maxn][maxn][maxn];  //判重同时也记录距离 int ID(int a,int b,int c)  //三位数变为一位数记录 
{return (a<<16)|(b<<8)|c;  //0|1=1; 
}
bool conflict(int a,int b,int a2,int b2)
{return b2==a2||(a==b2&&b==a2);  //不能穿过和重合 
}
int bfs()
{queue<int> q;q.push(ID(s[0],s[1],s[2]));memset(dis,-1,sizeof(dis));   //-1表示未走过 dis[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;   //1&0=0;  (A|0)&1=A;  if(a==t[0]&&b==t[1]&&c==t[2]) return dis[a][b][c];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(dis[a2][b2][c2]!=-1) continue;dis[a2][b2][c2]=dis[a][b][c]+1;q.push(ID(a2,b2,c2));}}}}return -1;
}
int main()
{int w,h,n;while(scanf("%d%d%d",&w,&h,&n)==3&&n){getchar();         //读取一个回车 char maze[20][20];for(int i=0;i<h;i++) fgets(maze[i],20,stdin);    //比scanf("%c",&maze[i][j])方便或者用cin>>maze[i]但比较慢; int cnt=0,x[maxn],y[maxn],id[maxs][maxs];for(int i=0;i<h;i++)for(int j=0;j<w;j++){char ch=maze[i][j];if(ch!='#'){x[cnt]=i;y[cnt]=j;      //记录每个编号的横纵坐标,方便等下记录与其连接的节点 id[i][j]=cnt;            //对每个非'#'节点编号,cnt表示编号 if(islower(ch)) s[ch-'a']=cnt;else if(isupper(ch)) t[ch-'A']=cnt;cnt++;}}const int dx[]={1,-1,0, 0,0};  //可以走五步 const int dy[]={0, 0,1,-1,0};for(int i=0;i<cnt;i++){deg[i]=0;for(int dir=0;dir<5;dir++)  {int nx=x[i]+dx[dir];int ny=y[i]+dy[dir];if(maze[nx][ny]!='#')  G[i][deg[i]++]=id[nx][ny];   //G[i][0~deg[i]-1]是保存从i点出发可到的点; }}if(n<=2) deg[cnt]=1,G[cnt][0]=cnt,s[2]=t[2]=cnt++;  //对于不到3个的,新建一个点,起点终点在同一位置且该点只能到该点,即自环 if(n<=1) deg[cnt]=1,G[cnt][0]=cnt,s[1]=t[1]=cnt++;printf("%d\n",bfs());}return 0;
}


单向稍微改编就能得到双向BFS

正着搜一个节点,反着搜一个节点 ,当遇到正反搜索都搜过的节点就表明已经找到解了。

接下来是双向BFS代码

用时从940ms变为550ms


#include<cstdio>
#include<cstring>
#include<cctype>
#include<queue>
using namespace std;
const int maxs=20;
const int maxn=200;int s[3],t[3];
int deg[maxn],G[maxn][5];  //G[][]是邻接表保存,deg[i]保存第i个点有几个相连点 
int dis[maxn][maxn][maxn],dis_pback[maxn][maxn][maxn];  //判重同时也记录距离 int ID(int a,int b,int c)  //三位数变为一位数记录 
{return (a<<16)|(b<<8)|c;  //0|1=1; 
}
bool conflict(int a,int b,int a2,int b2)
{return b2==a2||(a==b2&&b==a2);  //不能穿过和重合 
}
int bfs(queue<int>& q,int d[maxn][maxn][maxn]) {int u = q.front(); q.pop();int a = (u>>16)&0xff, b = (u>>8)&0xff, c = u&0xff;if(dis[a][b][c]!=-1&&dis_back[a][b][c]!=-1) return dis[a][b][c]+dis_back[a][b][c]; // 当遇到正反搜索都搜过的节点就表明已经找到解了 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 solve()
{queue<int> q;q.push(ID(s[0],s[1],s[2]));memset(dis,-1,sizeof(dis)); dis[s[0]][s[1]][s[2]]=0; queue<int> q_back;q_back.push(ID(t[0],t[1],t[2]));  //终点出发memset(dis_back,-1,sizeof(dis_back));  dis_back[t[0]][t[1]][t[2]]=0;int ans;while(1)   //正着搜一个节点,反着搜一个节点 {ans=bfs(q,dis);if(ans!=-1) return ans;ans=bfs(q_back,dis_back);if(ans!=-1) return ans;}return -1;
}
int main()
{int w,h,n;while(scanf("%d%d%d",&w,&h,&n)==3&&n){getchar();         //读取一个回车 char maze[20][20];for(int i=0;i<h;i++) fgets(maze[i],20,stdin);    //比scanf("%c",&maze[i][j])方便或者用cin>>maze[i]但比较慢; int cnt=0,x[maxn],y[maxn],id[maxs][maxs];for(int i=0;i<h;i++)for(int j=0;j<w;j++){char ch=maze[i][j];if(ch!='#'){x[cnt]=i;y[cnt]=j;      //记录每个编号的横纵坐标,方便等下记录与其连接的节点 id[i][j]=cnt;            //对每个非'#'节点编号,cnt表示编号 if(islower(ch)) s[ch-'a']=cnt;else if(isupper(ch)) t[ch-'A']=cnt;cnt++;}}const int dx[]={1,-1,0, 0,0};  //可以走五步 const int dy[]={0, 0,1,-1,0};for(int i=0;i<cnt;i++){deg[i]=0;for(int dir=0;dir<5;dir++)  {int nx=x[i]+dx[dir];int ny=y[i]+dy[dir];if(maze[nx][ny]!='#')  G[i][deg[i]++]=id[nx][ny];   //G[i][0~deg[i]-1]是保存从i点出发可到的点; }}if(n<=2) deg[cnt]=1,G[cnt][0]=cnt,s[2]=t[2]=cnt++;  //对于不到3个的,新建一个点,起点终点在同一位置且该点只能到该点,即自环 if(n<=1) deg[cnt]=1,G[cnt][0]=cnt,s[1]=t[1]=cnt++;printf("%d\n",solve());}return 0;
}



  相关解决方案