1. 程式人生 > >HDU-1043:Eight(八數碼+bfs(反向或A*))

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;  
}  



相關推薦

HDU-1043Eight數碼+bfs反向A*

題目大意: 給你一個3*3的表,中間有0到8  9個數字。每次你可以使用0和其相鄰的數字交換。使得最後得到一種題目要求的狀態並求出最短路徑。 解題思路: 當然想到的就是bfs求最短路徑,但是要解決幾個問題,用什麼存當前的狀態,map會超時,所以要用hash,hash可

HDU - 1043 - Eight(經典數碼&amp;&amp;各種搜尋) (未完)

關於逆序數判別是否有解 單向BFS #include <bits/stdc++.h> using namespace std; typedef pair<int,char> pic; struct Node { int s[9];//當前排列

【 HDU1043-經典BFS+康拓展開 數碼待更

給定一個序列,由1~8數字和字母x組成,表示的是一個3*3的矩形。每次操作x都能與相鄰的數字交換,問如何操作才能使得序列為{1,2,3,4,5,6,7,8,x}。   //多組資料-需要計算全部路徑後直接輸出 //反向搜尋+打表(離線) #include<iostream&

藍橋杯 歷屆試題 九宮重排 數碼問題--康託展開去重 + bfs搜尋

題意: 簡單的八數碼問題: 給你兩個狀態 求最少步數。 可以把點變成9: 這樣,9個數都不一樣,相當於是階乘的排列。 直接用bfs 搜尋 康託展開去重即可。 #include <cstdio> #include <cstring> #include

紫書—數碼問題BFS

sed 計算 cstring 藍橋杯 需要 amp 代碼 scan size 八數碼問題 編號為1~8的8個正方形滑塊被擺成3行3列(有一個格子空留),如圖所示。每次可以把與空格相鄰的滑塊(有公共邊才算相鄰)移到空格中,而它原來的位置就稱為了 新的空格。給定

路勁尋找-數碼問題判重

題目:編號為1~8的8個正方形滑塊被擺成3行3列(有一個格子留空)。把一種狀態變成另一種狀態最少移動多少步,到達不了,就輸出-1。 2 6 4 1 3 7  

數碼問題劉汝佳版

state const r++ return 可用 八數碼 ext ear ans 可以采用dfs,對空白點進行操作,然後可用編碼法,哈希表或者集合來標記,代碼如下 #include<iostream> #include<algorithm> #i

數碼難題啟發式搜尋

八數碼難題---啟發式搜素1.啟發式搜尋:特點:重排OPEN表,選擇最有希望的節點加以擴充套件種類:有序搜尋(A演算法)、A*演算法等2.估價函式用來估算節點處於最佳求解路徑上的希望程度的函式f(n) = g(n) + h(n) n——搜尋圖中的某個當前被擴充套件的節點;f(

hdu1043數碼 bfs 打表/雙向bfs/A*+康託判重+逆序奇偶剪枝

寫之前拜讀了這篇文章:八數碼的八境界 個人覺得寫順序為 一(可寫可不寫,介意找工作的的人最好試試這種寫法)-->三 -->二 -->四 -> 六-->八 境界一、逆向廣搜+STL 多組輸入輸出,可以想到打表,bfs時間複雜度為9!,查詢複雜度

A*演算法解決數碼問題C++版本

八數碼問題定義: 八數碼問題也稱為九宮問題。在3×3的棋盤,擺有八個棋子,每個棋子上標有1至8的某一數字,不同棋子上標的數字不相同。棋盤上還有一個空格,與空格相鄰的棋子可以移到空格中。要求解決的問題是:給出一個初始狀態和一個目標狀態,找出一種從初始轉變成

啟發式搜尋演算法求解數碼問題C

下午看一個遊戲的演算法時看了一下啟發式搜尋演算法,心血來潮跑了一遍很久很久以前寫八數碼的程式(C語言),發現各種問題,後來順著思路整理了一下,貼出來和大家分享一下,直接上程式碼: // // main.c // yunsuan // // Created by ma

UVA11624 Fire!兩次bfs,第一次預處理

題意翻譯 大火蔓延的迷宮 題目大意 你的任務是幫助Joe走出一個大火蔓延的迷宮。Joe每分鐘可以走到上下左右4個方向的相鄰格子之一,而所有著火的格子都會四周蔓延(即如果某個空格子與著火格子有公共邊,則下一分鐘這個空格子將著火)。迷宮中有一些障礙格,Joe和火都無法進入。當Joe走到一個迷宮的

大資料系列之hive、hive內建函式全解

1.內建運算子1.1關係運算符 運算子 型別 說明 A = B 所有原始型別 如果A

Spring Boot + Java爬蟲 + 部署到Linux、Nginx實現反向代理、動靜分離和websocket處理

    Nginx (engine x) 是一個高效能的HTTP和反向代理伺服器,也是一個IMAP/POP3/SMTP伺服器。所以,我們就用Nginx來實現反向代理和動靜分離的功能。    反向代理,通過搜尋、百科也可以大概知道。不過因為同為代理,所以總是和正向的代理區分不了

HDU 5468 Puzzled Elena 2015年上海賽區網路賽A

2.解題思路:本題利用dfs序+容斥原理+字首和性質解決。題目中要求每個結點,和多少個它的子結點互素。如果每次為了求一個點去跑一遍dfs,複雜度將是O(N(N+M))。一定會超時。因此需要深入加以分析。注意到n的範圍是10^5以內的,因此可以事先用線性篩求出每個數含有哪些

迷宮問題 與 最短路徑 怎樣記錄路徑的總結dijikstra,bfs,floyd,優先佇列

這次集訓做了幾個關於記錄路徑的問題,大體基於迪傑斯特拉(dijikstra)和弗洛伊德(floyd)演算法還有BFS廣搜。 記錄前驅要比記錄後驅更保險,因為從終點往起點追溯很容易,而從起點往後追溯有很

Spark Streaming 和kafka 整合指導kafka 0.8.2.1 以上版本

本節介紹一下如何配置Spark Streaming 來接收kafka的資料。有兩個方法: 1、老的方法 -使用Receivers 和kafka的高階API 2、新的方法( Spark 1.3 開始引入)-不適用Receivers。這兩個方式擁有不同的程式設計模型,效能特徵

Android APP中跳轉至微信,分享圖文給好友或者朋友圈加跳轉QQ好友QQ群

/**      * 分享圖片給好友      *      * @param file      */ private void shareToFriend(File file) {     Intent intent = new Intent();     ComponentName comp = new

HDU 1043 Eight數碼解題思路bfs+hash 打表 IDA* 等

中間 節點 sca 技巧 length div clu 鏈接 output 題目鏈接 https://vjudge.net/problem/HDU-1043 經典的八數碼問題,學過算法的老哥都會拿它練搜索 題意: 給出每行一組的數據,每組數據代表3*3的八數碼表,要求程序復

Eight HDU - 1043 數碼問題 康託展開 + 反向 bfs +記錄路徑

bfs 剪枝要點 visit陣列 hash函式(康託展開) 記憶化 bfs 打表儲存所有可達路徑 優先佇列 periority queue 多點同時bfs 反向bfs + bfs 打表儲存所有路徑 stl + 正向bfs