1. 程式人生 > >學習筆記--八數碼問題

學習筆記--八數碼問題

line 坐標 front urn 太行 size kkk syn 一點

題目鏈接

https://www.luogu.org/problemnew/show/P1379

分析

經典的八數碼問題,有雙向BFS和\(IDA*\)的方法,這裏使用的是\(A*\)啟發式搜索.

簡要介紹一下\(A*\),就是對於搜索的每一個狀態設計一個評估函數\(f(state)\),表示當前狀態\(state\)到目標狀態所需代價的估計值;還有一個\(g(state)\),表示當前狀態\(state\)到目標狀態實際需要的最小代價,\(A*\)中必須保證\(f(state)<=g(state)\)才能確保在目標狀態第一次被取出時就是最優解(實際一點,比如最少的步數)並且在搜索中每個狀態只需擴展一次,設計的估價函數\(f(stste)\)

越接近\(g(state)\)效率越高.

我們這裏用曼哈頓距離設計估價函數,也就是\(f(state)=\)$\sum^9_{i=1} (|posx_i-goalx_i|+|posy_i-goaly_i|) $

\(posx_i\)表示\(i\)這個數字在九宮格中的橫坐標,\(posy_i\)也就類似的。註意,我們不能統計\(0\),否則這樣\(f(state)\)可能會大於實際代價

為了確保每個狀態都被拓展一次,我們可以采用康托展開(將\(1-n\)的全排列映射成\(1-n!\)中的一個數)或是哈希表(unordered_map/pb_ds::gp_hash_table/map)

同時還要註意,八數碼問題有時候是沒有解的,我們將九宮格除空格之外的數按從左到右,再從上到下的順序排成一列數來表示每一個狀態,如果初始狀態和目標狀態的逆序對個數奇偶性不同的話是無解的,可以提前判斷一下是否有解來提高效率

題目:

P1379 八數碼難題

題目鏈接:https://www.luogu.org/problemnew/show/P1379

非常簡單,甚至不用判斷無解

代碼:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <queue>
#include <utility>
#include <cmath>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#define ll long long 
#define ri register int 
#define ull unsigned long long 
using std::pair;
using std::swap;
using std::abs;
using std::priority_queue;
using namespace __gnu_pbds;
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while(!isdigit(c=getchar()))ne=c==‘-‘;
    x=c-48;
    while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    x=ne?-x:x;return ;
}
const int maxn=5;
const int inf=0x7fffffff;
const int dx[4]={-1,0,0,1},dy[4]={0,-1,1,0};
int sta[maxn][maxn];
ll st,goal;
gp_hash_table <ll,bool>g;
pair<int,int>pos[10];
struct Sta{
    ll a;
    int s,f;
    Sta(){;}
    Sta(ll _a,int _s,int _f){a=_a,s=_s,f=_f;}
    bool operator <(const Sta &b)const{
        return f>b.f;
    }
};
int bx,by;//0位置 
inline int get_f(){//估價函數
    int ans=0;
    for(ri i=1;i<=3;i++){
        for(ri j=1;j<=3;j++){
            if(state[i][j])ans+=abs(i-pos[sta[i][j]].first)+abs(j-pos[sta[i][j]].second);
        }
    }
    return ans;
}
inline ll turn_num(){//轉為數字
    ll ans=0;
    for(ri i=1;i<=3;i++){
        for(ri j=1;j<=3;j++){
            ans=ans*10+sta[i][j];
        }
    }
    return ans;
}
inline void turn_sta(ll num){//轉為九宮格
    for(ri i=3;i>=1;i--){
        for(ri j=3;j>=1;j--){
            sta[i][j]=num%10;
            num=num/10;
            if(!sta[i][j])bx=i,by=j;
        }
    }
    return ;
}
inline void astar(){
    ll now,nxt;
    int x,y,z;
    Sta tmp;
    priority_queue<Sta>q;
    while(q.size())q.pop();
    turn_sta(st);
    q.push(Sta(st,0,get_f()));  
    g[st]=1;
    while(q.size()){
        tmp=q.top();q.pop();
        now=tmp.a,z=tmp.s;
        if(now==goal){
            printf("%d\n",z);
            return ;
        }
        turn_sta(now);
        for(ri i=0;i<4;i++){
            x=bx+dx[i],y=by+dy[i];
            if(x>=1&&x<=3&&y>=1&&y<=3){
                swap(sta[bx][by],sta[x][y]);
                nxt=turn_num();
                if(g[nxt]){
                    swap(sta[bx][by],sta[x][y]);
                    continue;
                }
                g[nxt]=1;
                q.push(Sta(nxt,z+1,z+1+get_f()));
                swap(sta[bx][by],sta[x][y]);
            }
        }
    }
    puts("unsolvable");
    return ;
}
int main(){
    /*230187546*/
    int x,y,z;
    pos[0].first=2,pos[0].second=2;
    pos[1].first=1,pos[1].second=1;
    pos[2].first=1,pos[2].second=2;
    pos[3].first=1,pos[3].second=3;
    pos[4].first=2,pos[4].second=3;
    pos[5].first=3,pos[5].second=3;
    pos[6].first=3,pos[6].second=2;
    pos[7].first=3,pos[7].second=1;
    pos[8].first=2,pos[8].second=1;
    read(st);
    goal=123804765;
    astar();
    return 0;
}

POJ1077 Eight

題目鏈接:http://poj.org/problem?id=1077

要打印方案,我用了一個\(naive\)的方法,就是記錄每個狀態是從哪個狀態轉移過來的(\(A*\)保證擴展到每一個狀態時一定是花費最少的步數),同時再用一個哈希表記錄它是進行哪個操作轉移過來的,最後遞歸打印即可

當然還有其他方法這裏不贅述.由於POJ好像不資瓷pbds和unordered_map,只好用map

代碼:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <queue>
#include <utility>
#include <cmath>
#include <map> 
#define ll long long 
#define ri register int 
#define ull unsigned long long 
using namespace std;
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while(!isdigit(c=getchar()))ne=c==‘-‘;
    x=c-48;
    while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    x=ne?-x:x;return ;
}
const int maxn=5;
const int inf=0x7fffffff;
const int dx[4]={-1,0,0,1},dy[4]={0,-1,1,0};
const char ch[4]={‘u‘,‘l‘,‘r‘,‘d‘};
int sta[maxn][maxn];
ll st,goal;
map <ll,ll> pre;
map <ll,int> dir;
pair<int,int>pos[10];
struct Sta{
    ll a;
    int s,f;
    Sta(){;}
    Sta(ll _a,int _s,int _f){a=_a,s=_s,f=_f;}
    bool operator <(const Sta &b)const{
        return f>b.f;
    }
};
int bx,by;//0位置 
inline int get_f(){
    int ans=0;
    for(ri i=1;i<=3;i++){
        for(ri j=1;j<=3;j++){
            if(sta[i][j]==0)continue;
            ans+=abs(i-pos[sta[i][j]].first)+abs(j-pos[sta[i][j]].second);
        }
    }
    return ans;
}
inline ll turn_num(){
    ll ans=0;
    for(ri i=1;i<=3;i++){
        for(ri j=1;j<=3;j++){
            ans=ans*10+sta[i][j];
        }
    }
    return ans;
}
inline void turn_sta(ll num){
    for(ri i=3;i>=1;i--){
        for(ri j=3;j>=1;j--){
            sta[i][j]=num%10;
            num=num/10;
            if(!sta[i][j])bx=i,by=j;
        }
    }
    return ;
}
void print(ll x){
    /*234150768*/
    if(x==st)return ;   
    print(pre[x]);
    putchar(ch[dir[x]]);
    return ;
}
inline void astar(){
    ll now,nxt;
    int x,y,z;
    Sta tmp;
    priority_queue<Sta>q;
    while(q.size())q.pop();//puts("wtf");
    turn_sta(st);
    q.push(Sta(st,0,get_f()));  
    while(q.size()){
        tmp=q.top();q.pop();
        now=tmp.a,z=tmp.s;
        if(now==goal){
            //printf("%d\n",z);
            //printf("%lld***\n***",pre[st]);
            print(goal);
            return ;
        }
        //printf("*%lld\n",now);
        turn_sta(now);
        for(ri i=0;i<4;i++){
            x=bx+dx[i],y=by+dy[i];
            if(x>=1&&x<=3&&y>=1&&y<=3){
                swap(sta[bx][by],sta[x][y]);
                nxt=turn_num();
                if(!pre[nxt]){
                    pre[nxt]=now;
                    dir[nxt]=i;
                    q.push(Sta(nxt,z+1,z+1+get_f()));
                }
                swap(sta[bx][by],sta[x][y]);
            }
        }
    }
    puts("unsolvable");
    return ;
}
int main(){
    int num[10];
    char x[2];
    pos[0].first=3,pos[0].second=3;
    pos[1].first=1,pos[1].second=1;
    pos[2].first=1,pos[2].second=2;
    pos[3].first=1,pos[3].second=3;
    pos[4].first=2,pos[4].second=1;
    pos[5].first=2,pos[5].second=2;
    pos[6].first=2,pos[6].second=3;
    pos[7].first=3,pos[7].second=1;
    pos[8].first=3,pos[8].second=2;
    //read(st);
    st=0;
    for(ri i=1;i<=9;i++){
        scanf("%s",x);
        if(x[0]==‘x‘)st=st*10,num[i]=inf;
        else st=st*10+x[0]-‘0‘,num[i]=x[0]-‘0‘;
    }
    //printf("%lld\n",st);
    int cnt=0;
    for(ri i=1;i<=9;i++){
        if(num[i]==inf)continue;
        for(ri j=i+1;j<=9;j++){
            if(num[j]<num[i]){cnt++;}
        }
    }
    //printf("%d\n",cnt);
    if(cnt%2==1){
        puts("unsolvable");
    }
    else{
        pre[st]=19260817;
        goal=123456780;
        astar();
    }
    return 0;
}

UVA652 Eight

題目鏈接: https://cn.vjudge.net/problem/UVA-652

這道題由於多組數據發現各個\(A*\)好像不太行,於是就先一遍BFS擴展出所有狀態同時記錄路徑,這一次沒用哈希表用了康拓展開,然而不知道為何WA掉了。。。

代碼:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <queue>
#include <utility>
#include <iostream>
#define ll long long 
#define ri register int 
#define ull unsigned long long 
using namespace std;
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while(!isdigit(c=getchar()))ne=c==‘-‘;
    x=c-48;
    while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
    x=ne?-x:x;return ;
}
const int maxn=5;
const int inf=0x7fffffff;
const int dx[4]={-1,0,0,1},dy[4]={0,-1,1,0};
const char ch[4]={‘u‘,‘l‘,‘r‘,‘d‘};
const int fac[10]={1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
int sta[maxn][maxn];
ll st,goal;
struct Sta{
    ll a;int v;
    Sta(){;}
    Sta(ll _a,int _v){a=_a,v=_v;}
};
int bx,by;//0位置 
int num[10],len[500005];
char path[500005][75];
inline ll turn_num(){
    ll ans=0;
    for(ri i=1;i<=3;i++){
        for(ri j=1;j<=3;j++){
            ans=ans*10+sta[i][j];
            num[(i-1)*3+j]=sta[i][j];
        }
    }
    return ans;
}
inline int cal_cantor(){
    int sml=0,x=0;
    for(ri i=1;i<=9;i++){
        sml=0;
        for(ri j=i+1;j<=9;j++){
            if(num[j]<num[i])++sml;
        }
        x+=fac[9-i]*sml;
    }
    return x+1;
} 
inline void turn_sta(ll p){
    for(ri i=3;i>=1;i--){
        for(ri j=3;j>=1;j--){
            sta[i][j]=p%10;
            p=p/10;
            if(sta[i][j]==9)bx=i,by=j;
        }
    }
    return ;
}
bool vis[500005];
inline void bfs(){
    ll now,nxt;
    int x,y,z,pre_val,val;
    Sta tmp;
    queue<Sta>q;
    while(q.size())q.pop();//puts("wtf");   
    turn_sta(goal);
    z=turn_num();
    val=cal_cantor();
    vis[val]=1;
    q.push(Sta(goal,val));  
    while(q.size()){
        tmp=q.front();q.pop();
        now=tmp.a,pre_val=tmp.v;
        turn_sta(now);
        for(ri k=0;k<4;k++){
            x=bx+dx[k],y=by+dy[k];
            if(x>=1&&x<=3&&y>=1&&y<=3){
                swap(sta[bx][by],sta[x][y]);
                nxt=turn_num();
                val=cal_cantor();               
                //printf("%d %d %lld %d %d\n",x,y,nxt,val,pre_val);
                if(!vis[val]){
                    vis[val]=1;
                    for(ri i=1;i<=len[pre_val];i++){
                        path[val][i]=path[pre_val][i];
                    }
                    len[val]=len[pre_val];
                    path[val][++len[val]]=ch[k];
                    q.push(Sta(nxt,val));
                }
                swap(sta[bx][by],sta[x][y]);
            }
        }
    }
    return ;
}
int main(){
    /*234150768*/
    /*123456780*/
    /*
    2   
    2 3 4 1 5 x 7 6 8   
    2 3 4 1 5 x 7 6 8
    */
    std::ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    int t,val;
    char kkk;
    read(t);
    goal=123456789;
    memset(vis,0,sizeof(vis));
    bfs();
    while(t--){
        st=0;
        for(ri i=1;i<=9;i++){
            cin>>kkk;//scanf("%s",x);
            if(kkk==‘x‘)st=st*10,num[i]=9;
            else st=st*10+kkk-‘0‘,num[i]=kkk-‘0‘;
        }
        val=cal_cantor();
        //printf("%d %d\n",val,len[val]);
        if(!vis[val]){
            puts("unsolvable");
        }
        else{
            for(ri i=len[val];i>=1;i--)putchar(path[val][i]);
            puts("");
        }
        if(t)puts("");
    }
    return 0;
}

學習筆記--八數碼問題