【题意】
w*h(w、h<=16)网格上有n(n<=3)个小写字母(代表鬼)。要求把他们 分别移动到对应的大写字
母里。每步可以有多个鬼同时移动(可以往上下左右移动或不动),但每步结束之后任何两个鬼不能占用同
一个位置,也不能在一步之内交换位置。任意一个2*2子网格中至少有一个‘#’,鬼不能再‘#’上移动。输出最
少的步数,保证有解。
【分析】
此题用一般的bfs做肯定超时,刚开始想的有几个鬼就几次bfs,用数组标记每个鬼走的点的步数,最后相
加。后来仔细想了想这样做是非常麻烦且很难达到目标的。
1. 题目指出任意一个2*2子网格中最少有一个‘#’,表示这输入的网格中有很多‘#’,真正能走的路很少,所以
新定义一个数组,用来保存能走的点及其能走到哪几个点。
2. 表示坐标得用2个变量表示,有点麻烦,将坐标压缩成一个整数,方便使用和移动。
3. 题目有多个鬼同时移动,因为无法事先知道有几个鬼,不然就令鬼为3个,节约讨论的代码,省掉很多代
码。
4. 将3个鬼的坐标通过位运算压缩到1个整数里,方便搜索过程。
【代码】
方法一:单向BFS
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,k;
int book[200][200][200],G[200][5];
int deg[200];
int s[3],t[3];
int fang[5][2]={0,0,-1,0,0,-1,0,1,1,0};
inline bool fun(int a,int b,int a2,int b2)
{
return (a2==b2)||(a==b2&&b==a2); // 判断是否走到同一点或者两点交换了
}
inline int ID(int a,int b,int c)
{
return (a<<16) | (b<<8) | c; // 将三个坐标合成一个数
}
int BFS()
{
memset(book,-1,sizeof(book));
queue<int>p;
p.push(ID(s[0],s[1],s[2]));
book[s[0]][s[1]][s[2]]=0;
while(!p.empty())
{
int u=p.front();
p.pop();
int a=(u>>16)&0xff,b=(u>>8)&0xff,c=u&0xff; // 将合并的点拆分出来,进行移动
if(a==t[0]&&b==t[1]&&c==t[2])
return book[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(fun(a,b,a2,b2))
continue;
for(int k=0;k<deg[c];k++)
{
int c2=G[c][k];
if(fun(a,c,a2,c2)||fun(b,c,b2,c2))
continue;
if(book[a2][b2][c2]==-1)
{
book[a2][b2][c2]=book[a][b][c]+1;
p.push(ID(a2,b2,c2));
}
}
}
}
}
return -1;
}
int main()
{
while(scanf("%d %d %d",&m,&n,&k)&&(n+m+k))
{
getchar();
memset(G,0,sizeof(G));
memset(deg,0,sizeof(deg));
char e[20][20];
for(int i=0;i<n;i++)
fgets(e[i],18,stdin);
int cnt=0,id[20][20]={0},x[200]={0},y[200]={0};
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(e[i][j]!='#') // 将不是'#'的数据分离出来,节约搜索时间,不然超时
{
x[cnt]=i;
y[cnt]=j;
id[i][j]=cnt;
if(e[i][j]>='a'&&e[i][j]<='c')
s[e[i][j]-'a']=cnt;
else if(e[i][j]>='A'&&e[i][j]<='C')
t[e[i][j]-'A']=cnt;
cnt++;
}
}
}
for(int i=0;i<cnt;i++) // 进行非‘#’位置的移动,判断可以的的方向的个数及其坐标,节约时间
{
deg[i]=0;
for(int j=0;j<5;j++)
{
int tx,ty;
tx=x[i]+fang[j][0];
ty=y[i]+fang[j][1];
if(e[tx][ty]!='#')
G[i][deg[i]++]=id[tx][ty];
}
}
// 如果不够3个就补上,方便进行操作,不然还得进行3种讨论
if(k<=2)
{
deg[cnt]=1;
G[cnt][0]=cnt;
s[2]=t[2]=cnt++;
}
if(k<=1)
{
deg[cnt]=1;
G[cnt][0]=cnt;
s[1]=t[1]=cnt++;
}
printf("%d\n",BFS());
}
return 0;
}
方法二:双向BFS
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,k;
int book[200][200][200],vis[200][200][200],G[200][5];
int deg[200];
int s[3],t[3];
int fang[5][2]= {0,0,-1,0,0,-1,0,1,1,0};
inline bool fun(int a,int b,int a2,int b2)
{
return (a2==b2)||(a==b2&&b==a2); // 判断是否走到同一点或者两点交换了
}
inline int ID(int a,int b,int c)
{
return (a<<16) | (b<<8) | c; // 将三个坐标合成一个数
}
int BFS()
{
memset(book,0,sizeof(book));
memset(vis,0,sizeof(vis));
queue<int>p,q;
p.push(ID(s[0],s[1],s[2]));
q.push(ID(t[0],t[1],t[2]));
book[s[0]][s[1]][s[2]]=0;
book[t[0]][t[1]][t[2]]=1;
vis[s[0]][s[1]][s[2]]=1;
vis[t[0]][t[1]][t[2]]=2;
while(!p.empty()||!q.empty())
{
int len1=p.size();
while(len1--) // 双向BFS是按层搜索的,不然会出错,可能一个环上面要4步,下面要3步,如果正反按步搜索,你走一步我走一步就会出错
{
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(fun(a,b,a2,b2))
continue;
for(int k=0; k<deg[c]; k++)
{
int c2=G[c][k];
if(fun(a,c,a2,c2)||fun(b,c,b2,c2))
continue;
if(vis[a2][b2][c2]==0)
{
vis[a2][b2][c2]=1; // 正标记
book[a2][b2][c2]=book[a][b][c]+1;
p.push(ID(a2,b2,c2));
}
else if(vis[a2][b2][c2]==2) // 如果反向走过此点,就说明构成了最短路
return book[a][b][c]+book[a2][b2][c2];
}
}
}
}
int len2=q.size();
while(len2--)
{
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(fun(a,b,a2,b2))
continue;
for(int k=0; k<deg[c]; k++)
{
int c2=G[c][k];
if(fun(a,c,a2,c2)||fun(b,c,b2,c2))
continue;
if(vis[a2][b2][c2]==0)
{
vis[a2][b2][c2]=2; // 反标记
book[a2][b2][c2]=book[a][b][c]+1;
q.push(ID(a2,b2,c2));
}
else if(vis[a2][b2][c2]==1) // 如果正向走过此点,就说明构成了最短路
return book[a][b][c]+book[a2][b2][c2];
}
}
}
}
}
return -1;
}
int main()
{
while(scanf("%d %d %d",&m,&n,&k)&&(n+m+k))
{
getchar();
memset(G,0,sizeof(G));
memset(deg,0,sizeof(deg));
char e[20][20];
for(int i=0; i<n; i++)
fgets(e[i],18,stdin);
int cnt=0,id[20][20]= {0},x[200]= {0},y[200]= {0};
for(int i=0; i<n; i++)
{
for(int j=0; j<m; j++)
{
if(e[i][j]!='#') // 将不是'#'的数据分离出来,节约搜索时间,不然超时
{
x[cnt]=i;
y[cnt]=j;
id[i][j]=cnt;
if(e[i][j]>='a'&&e[i][j]<='c')
s[e[i][j]-'a']=cnt;
else if(e[i][j]>='A'&&e[i][j]<='C')
t[e[i][j]-'A']=cnt;
cnt++;
}
}
}
for(int i=0; i<cnt; i++) // 进行非‘#’位置的移动,判断可以的的方向的个数及其坐标,节约时间
{
deg[i]=0;
for(int j=0; j<5; j++)
{
int tx,ty;
tx=x[i]+fang[j][0];
ty=y[i]+fang[j][1];
if(e[tx][ty]!='#')
G[i][deg[i]++]=id[tx][ty];
}
}
// 如果不够3个就补上,方便进行操作,不然还得进行3种讨论
if(k<=2)
{
deg[cnt]=1;
G[cnt][0]=cnt;
s[2]=t[2]=cnt++;
}
if(k<=1)
{
deg[cnt]=1;
G[cnt][0]=cnt;
s[1]=t[1]=cnt++;
}
printf("%d\n",BFS());
}
return 0;
}
【收获】
1. 当无用数据很多时,可以选择重新构建数组,只保存有用数据,节约搜索时间。
2. 搜索保存周围能走的点,结尾搜索时间,不过得用一个数组保存每个点能走到几个点,对可走方向比较少的数据用,否则此方法没太大作用。
3. 数据有部分关系,且范围不是太大,可以选择压缩到一个数据里面。
4. 双向BFS,双向BFS必须按层走,正向走完所有当前一步,再反向走完所以当前一步,不然会出错,例如S-->T,从上面走需要过3个点、4步,从下面走需要过2个点、3步,如果按点走的话,如果先从上面走就会出现错误。