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*。。。
题目大意:
给你一个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; }
相关文章推荐
- HDU 1043 Eight (BFS·八数码·康托展开)
- HDU1043 Eight(八数码:逆向BFS打表+康托展开)题解
- HDU 1043 Eight(经典八数码)(BFS+STL)
- HDU 1043 Eight(反向BFS打表+康托展开)
- HDU-1043 Eight八数码 搜索问题(bfs+hash 打表 IDA* 等)
- HDU 1043 Eight (BFS·八数码·康托展开)
- hdu1043 Eight(A*/双向BFS/单项BFS打表+康托展开)
- poj 1077 hdu 1043 Eight 八数码问题 DBFS(双向广度优先搜索)a*算法 康拓展开
- HDU1043 Eight 八数码问题
- hdu 1043 /poj 1077 Eight(经典八数码问题,BFS+康托展开)
- poj 1077 & hdu 1043 Eight ( 多种解法:预处理、bfs、dbfs、IDA*、A*)
- POJ1077&HDU1043 Eight 八数码第八境界 IDA* hash 康托展开 奇偶剪枝
- hdu 1043 八数码 经典搜索问题 BFS+MAP
- Hdu1043 Eight 【A*搜索】八数码问题
- hdu 1043 Eight(双向bfs)
- hdu 1043 Eight(BFS经典)
- hdu 1043 ,pku 1077 Eight ,八数码问题
- HDU 1043 eight (Astar 八数码)
- hdu 1043 eight(poj 1077) (bfs)
- hdu 1043-Eight(经典八数码问题)(单向广搜 A* 状态压缩)