双向bfs(什么魔鬼题)
双向BFS本身不难,这道题的处理比较巧妙
1、为了方便代码处理,鬼数不足3的时候也要填充
2、首先如何表示一个鬼当前在的位置:将每个可以在的位置设置标号
3、如何表示一个状态(即三个鬼的位置):将三个鬼所在标号进行处理成一个能标识这三个值的唯一值 二进制压缩
这个比较难想,可以把三个鬼的位置开成结构体扔进队列里面
4、队列中存放的是一个状态,一个数字,如何标记该状态已经被使用过:d1[N][N][N]来标记
//又双叒叕死在 死因dist 和color数组开小了 挺好的
//读入的时候忘记读回车,感觉自己还是太菜辽 读入都不会
/*
与上面版本都是550ms
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=20;
const int M=200;
int deg[M];
int G[M][5]; //每个点最多有5个可留的位置
int dx[5]={1,-1,0,0,0};
int dy[5]={0,0,1,-1,0};
int d1[M][M][M];
int d2[M][M][M];
char a[N][N];
int s[3],t[3];
int w,h,n;
int ID(int a,int b,int c)
{
return (a<<16)|(b<<8)|c;
}
bool conflict(int a1,int b1,int a2,int b2) //分别代表鬼A和鬼B的原位置和目标位置
{
return (a2==b2)||(a1==b2)&&(a2==b1); //两只鬼要到达同一位置或者要交换位置,这种操作是不允许的
}
int bfs()
{
queue<int>q1,q2;
q1.push(ID(s[0],s[1],s[2]));
q2.push(ID(t[0],t[1],t[2]));
d1[s[0]][s[1]][s[2]]=0;//三个鬼到达某一状态需要多少步
d2[t[0]][t[1]][t[2]]=0;//该版本记录从后往前多少步
while(!q1.empty()||!q2.empty())//不是&&
{
int fnum1=q1.size(),fnum2=q2.size();
while(fnum1--)
{
int u=q1.front();q1.pop();
int a=(u>>16)&0xff,b=(u>>8)&0xff,c=u&0xff; //恢复三只鬼的位置在的编号
if(a==t[0]&&b==t[1]&&c==t[2]) //有了快了10ms
return d1[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(d1[a2][b2][c2]==-1)
{
d1[a2][b2][c2]=d1[a][b][c]+1;
q1.push(ID(a2,b2,c2));
if(d2[a2][b2][c2]!=-1) //如果反向搜已经搜到了,已经找到答案了
return d1[a2][b2][c2]+d2[a2][b2][c2];
}
}
}
}
}
while(fnum2--)
{
int u=q2.front();q2.pop();
int a=(u>>16)&0xff,b=(u>>8)&0xff,c=u&0xff;
if(a==s[0]&&b==s[1]&&c==s[2])
return d2[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(d2[a2][b2][c2]==-1)
{
d2[a2][b2][c2]=d2[a][b][c]+1;
q2.push(ID(a2,b2,c2));
if(d1[a2][b2][c2]!=-1)
return d1[a2][b2][c2]+d2[a2][b2][c2];
}
}
}
}
}
}
}
int main()
{
while(scanf("%d%d%d\n",&w,&h,&n)==3&&n)
{
int cnt=0;
int x[M],y[M];
int id[N][N];
//我连读入都不会了qwq,cin读不进来空格
for(int i=0;i<h;++i)
{
for(int j=0;j<w;++j)
a[i][j]=getchar();
getchar();
}
for(int i=0;i<h;++i) //把可以走的点打标签
for(int j=0;j<w;++j)
{
if(a[i][j]!='#')
{
x[cnt]=i;
y[cnt]=j;
id[i][j]=cnt;
if(islower(a[i][j]))
s[a[i][j]-'a']=cnt; //把鬼也打上0 1 2的标签
else if(isupper(a[i][j]))
t[a[i][j]-'A']=cnt;
cnt++;
}
}
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];
if(a[nx][ny]!='#')
G[i][deg[i]++]=id[nx][ny]; //G存的是到了一个位置能走的节点数
}
}
if(n<=2)
{
deg[cnt]=1; G[cnt][0]=cnt; s[2]=t[2]=cnt++; //差点忘记cnt要++
}
if(n<=1)
{
deg[cnt]=1; G[cnt][0]=cnt; s[1]=t[1]=cnt++;
}
memset(d1,-1,sizeof(d1));
memset(d2,-1,sizeof(d2));
printf("%d\n",bfs());
}
}
/*
5 5 2
#####
#A#B#
# #
#b#a#
#####
16 4 3
################
## ########## ##
# ABCcba #
################
16 16 3
################
### ## # ##
## # ## # c#
# ## ########b#
# ## # # # #
# # ## # # ##
## a# # # # #
### ## #### ## #
## # # # #
# ##### # ## ##
#### #B# # #
## C# # ###
# # # ####### #
# ###### A## #
# # ##
################
0 0 0
*/
普通bfs版本
//存储数据的时候,存了一个点的横纵坐标和编号
//脑子不清醒最后一个样例没过
//860ms
#include<iostream>
#include<string.h>
#include<string>
#include<queue>
#include<cstdio>
using namespace std;
int w,h,n;
const int N=20;
int id[N][N];
const int M=200;
int dx[5]={1,-1,0,0,0};
int dy[5]={0,0,1,-1,0};
int s[3],t[3];
int G[M][5];
int deg[M];
int d[M][M][M];
char a[N][N];
int ID(int a,int b,int c)
{
return (a<<16)|(b<<8)|c;
}
bool conflict(int a1,int b1,int a2,int b2)
{
return a2==b2||(b1==a2)&&(a1==b2); //如果两鬼占用同一位置,或者两鬼在一步之内交换了位置,不合法
}
//代码死了两次,一次是读入,没有注意到在读fgets时需要读空格,其次是在给虚拟节点赋值的时候把个数赋了0,应该是1 答案return -1方便了检查代码
int bfs()
{
queue<int>q;
memset(d,-1,sizeof(d));
//将三个状态映射为一个值方便入队,这是什么魔鬼操作
// cout<<ID(s[0],s[1],s[2]);
q.push(ID(s[0],s[1],s[2]));
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; //c为ID的低8位,
//cout<<a<<" "<<b<<" "<<c<<endl;
if(a==t[0]&&b==t[1]&&c==t[2]) return d[a][b][c];
//cout<<deg[a]<<" "<<deg[b]<<" "<<deg[c]<<endl;
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;
q.push(ID(a2,b2,c2));
d[a2][b2][c2]=d[a][b][c]+1;
}
}
}
}
return -1;
}
int main()
{
while(~scanf("%d%d%d\n",&w,&h,&n)&&n) //把上一行的空格给读了 一定要先试试读入对不对 哭辽
{
//memset(deg,0,sizeof(deg));
// for(int i=0;i<h;++i)
// for(int j=0;j<w;++j)
// cin>>a[i][j];
for(int i=0;i<h;++i)
fgets(a[i],20,stdin);
//cout<<"?"<<endl;
int x[M],y[M];
int cnt=0;
for(int i=0;i<h;++i)
for(int j=0;j<w;++j)
{
if(a[i][j]!='#') //记录每个合法节点的横纵坐标和编号 为了后面记录每个节点有多少种走法以及它的相邻节点
{
x[cnt]=i;
y[cnt]=j;
id[i][j]=cnt;
if(islower(a[i][j]))
s[a[i][j]-'a']=cnt;
else if(isupper(a[i][j]))
t[a[i][j]-'A']=cnt;
++cnt;
}
}
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];
if(a[nx][ny]!='#')
G[i][deg[i]++]=id[nx][ny];
}
}
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;
}