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-1043:Eight(八數碼+bfs(反向或A*))
題目大意: 給你一個3*3的表,中間有0到8 9個數字。每次你可以使用0和其相鄰的數字交換。使得最後得到一種題目要求的狀態並求出最短路徑。 解題思路: 當然想到的就是bfs求最短路徑,但是要解決幾個問題,用什麼存當前的狀態,map會超時,所以要用hash,hash可
HDU - 1043 - Eight(經典八數碼&&各種搜尋) (未完)
關於逆序數判別是否有解 單向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