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

//测试数据 
/*
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;
}



你可能感兴趣的:(UVa 1601 The morning after Halloween(单向BFS+双向BFS))