您的位置:首页 > 其它

HDU-1043:Eight(八数码+bfs(反向或A*))

2017-07-03 17:36 399 查看
题目链接:点击打开链接

题目大意:

给你一个3*3的表,中间有0到8  9个数字。每次你可以使用0和其相邻的数字交换。使得最后得到一种题目要求的状态并求出最短路径。

解题思路:

当然想到的就是bfs求最短路径,但是要解决几个问题,用什么存当前的状态,map会超时,所以要用hash,hash可以用康托展开。但是如果裸hash+正向bfs肯定会超时。做法有很多,我这里说两种做法。

第一种是利用A*算法,优先判断某些状态,就可以实现剪枝,但是这样出来的答案样例都过不了,最短路步数都不一样。但是AC了,费解费解。。

更新更新!!!

发现了原来代码错误的地方,估价函数的两个估价值不应该像原来的那样写,用他们的和来表示的话样例就能过了,也没测其他数据,目前应该是这个问题,要用两个关键字的和来估价,而不能用优先级分开估价..以下为正确的代码,错误的地方会注释标出

以下贴代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
using namespace std;

int m, n;
int ha[10]={1,1,2,6,24,120,720,5040,40320};
int dx[4] = { -1,1,0,0 };
int dy[4] = { 0,0,-1,1 };
int us[1000000][2];
char a2[30];
int a1[30];
string ans;
struct node
{
int f[3][3];
int x;
int y;
int h,g;
bool operator<(const node n1)const{ //优先队列第一关键字为h,第二关键字为g
return h+g>(n1.h+n1.g); //注意!!!更新版估价函数
//return h!=n1.h?h>n1.h:g>n1.g; 原来的估价根据优先级判断 错了,样例没过但A了
}
int step;
int haha;
string str; //记录路径
}start,en;
int get_hash(node e) //得到每种状态所对应的哈希值,用了康托展开。
{
int a[9],k=0,ans=0;
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
a[k++]=e.f[i][j];
}
for(int i=0;i<9;i++)
{
k=0;
for(int j=0;j<i;j++)
if(a[j]<a[i]) k++;
ans=ans+ha[i]*k;
}
return ans;
}
string vs(int i) //记录路径,神奇的办法,原本准备双向bfs,后来发现这样记忆路径搞不出来,遂最终用了单向bfs。
{
if(i==0)
return "u";
else if(i==1)
return "d";
else if(i==2)
return "l";
else if(i==3)
return "r";
}
int getH(node n) //A*算法,计算当前状态和最终状态的差值,记为H
{
int result = 0 ;
for(int i = 0 ; i < 3 ; ++i)
{
for(int j = 0 ; j < 3 ; ++j)
{
if(n.f[i][j])
{

4000
int x = (n.f[i][j]-1)/3 , y = (n.f[i][j]-1)%3 ;
result += abs(x-i)+abs(y-j) ;
}
}
}
return result ;
}
string bfs() //单向bfs()过程,用了优化队列,也就是A*算法
{
priority_queue<node> que;
que.push(start);
while(que.size())
{
node k=que.top();
que.pop();
if(k.haha==en.haha)
return k.str;
for(int i=0;i<4;i++)
{
node v;
v=k;
v.x=k.x+dx[i];
v.y=k.y+dy[i];
if(v.x>=0&&v.x<3&&v.y>=0&&v.y<3)
{
swap(v.f[k.x][k.y],v.f[v.x][v.y]);
v.haha=get_hash(v);
if(us[v.haha][v.step]==1) continue;
v.h=getH(v);
v.g++;
v.str=k.str+vs(i);
us[v.haha][v.step]=1;
que.push(v);
if(v.haha==en.haha)
return v.str;
}
}
}
}

int main()
{
while(scanf(" %c",&a2[0])!=EOF)
{
for(int i=1;i<9;i++)
scanf(" %c",&a2[i]);
memset(us,0,sizeof(us));
for(int i=0;i<9;i++) //预处理得到起始状态和结束状态的哈希值
{
if(a2[i]=='x')
a2[i]='0';
a1[i]=a2[i]-'0';
}
for(int i=0;i<9;i++)
{
start.f[i/3][i%3]=a1[i];
if(a1[i]==0)
{
start.x=i/3;
start.y=i%3;
}
}
start.step=0;
start.haha=get_hash(start);
start.h=getH(start);
start.g=0;
us[start.haha][0]=1;
int a3[10]={1,2,3,4,5,6,7,8,0};
for(int i=0;i<9;i++)
{
en.f[i/3][i%3]=a3[i];
if(a3[i]==0)
{
en.x=i/3;
en.y=i%3;
}
}
en.step=1;
en.haha=get_hash(en);
us[en.haha][1]=1;
en.h=getH(en);
int sum=0;
for(int i=0;i<9;i++)
{
if(a2[i]=='0')continue;
for(int j=0;j<i;j++)
{
if(a2[j]=='0')continue;
if(a2[j]>a2[i]) sum++;
}
}
if(sum&1)
printf("unsolvable\n");
else
cout<<bfs()<<endl;
}
}


第二种做法是反向bfs

妈的,以前刚写这道题的时候还用过反向bfs,当时我还在想这反向bfs有个屁用啊。不跟正向一样嘛,后来做的一道类似的题目启发了我,我擦,反向bfs完全不是那么用的啊。反向bfs实现的是预处理,对所有初始状态打表,所以打完表后可以直接输出结果就是了,唉,自己真的太傻了。不过也不想写了,就直接贴xh学长的代码了,这种方法速度很快,毕竟预处理之后剩下输出是O(1)的,样例也能过,反正比第一种方法快很多就是了,理解起来也比较简单,当时傻了吧唧用什么A*。。。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <map>
using namespace std;
#define INF 0x3f3f3f3f
#define mem(a,b) memset((a),(b),sizeof(a))

const int MAXS=362880+3;//9!
const int MAXN=9;
const int sup[]={1,1,2,6,24,120,720,5040,40320};//阶乘表,用于康托展开
int maze[MAXN];//保存棋盘状态的临时数组
string s;
char ans[MAXS];//从上一个状态走到当前状态的移动方向
int path[MAXS];//走到的下一个状态
queue<int> que;
int init_id;//最终状态id

int get_id()//康托展开
{
int res=0;
for(int i=0;i<9;++i)
{
int cnt=0;//剩下中第几小
for(int j=i+1;j<9;++j)
if(maze[j]<maze[i])
++cnt;
res+=cnt*sup[8-i];
}
return res;
}

void get_statue(int id)//通过康托逆展开生成状态
{
int a[MAXN];//存剩下中第几小
bool used[MAXN];//是否已用
for(int i=8;i>=0;--i)
{
used[i]=false;
a[8-i]=id/sup[i];
id%=sup[i];
}
int cnt;
for(int i=0;i<MAXN;++i)
{
cnt=0;
for(int j=0;j<MAXN;++j)
if(!used[j])
{
if(cnt==a[i])
{
maze[i]=j;
used[j]=true;
break;
}
else ++cnt;
}
}
}

void init()//bfs倒推预处理出所有结果
{
mem(path,-1);
for(int i=0;i<MAXN;++i)
maze[i]=(i==8)?0:i+1;
init_id=get_id();
que.push(init_id);
while(!que.empty())
{
int now=que.front(); que.pop();
get_statue(now);
int p=-1;//x的位置
for(int i=0;i<MAXN;++i)
if(maze[i]==0)
p=i;
if(p!=0&&p!=3&&p!=6)//x左移
{
swap(maze[p],maze[p-1]);
int next=get_id();
if(next!=init_id&&path[next]==-1)//新状态
{
path[next]=now;
ans[next]='r';//因为是倒推,所以方向反向
que.push(next);
}
swap(maze[p],maze[p-1]);
}
if(p!=2&&p!=5&&p!=8)//x右移
{
swap(maze[p],maze[p+1]);
int next=get_id();
if(next!=init_id&&path[next]==-1)
{
path[next]=now;
ans[next]='l';
que.push(next);
}
swap(maze[p],maze[p+1]);
}
if(p<6)//x下移
{
swap(maze[p],maze[p+3]);
int next=get_id();
if(next!=init_id&&path[next]==-1)
{
path[next]=now;
ans[next]='u';
que.push(next);
}
swap(maze[p],maze[p+3]);
}
if(p>2)//x上移
{
swap(maze[p],maze[p-3]);
int next=get_id();
if(next!=init_id&&path[next]==-1)
{
path[next]=now;
ans[next]='d';
que.push(next);
}
swap(maze[p],maze[p-3]);
}
}
}

int main()
{
init();
cin.sync_with_stdio(false);//取消流同步
while(getline(cin,s))
{
int tmp=0;
for(int i=0;i<s.length();++i)
if(s[i]!=' ')
if(s[i]=='x')
maze[tmp++]=0;
else maze[tmp++]=s[i]-'0';
int id=get_id();
if(id!=init_id&&path[id]==-1)
cout<<"unsolvable";
while(path[id]!=-1)
{
cout<<ans[id];
id=path[id];
}
cout<<'\n';
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: