HDU-1043:Eight(八数码+bfs(反向或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])
            {
                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;  
}  




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值