1. 程式人生 > >Eight HDU - 1043 八數碼問題 康託展開 + 反向 bfs +記錄路徑

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

圖

bfs 剪枝要點

  1. visit陣列 hash函式(康託展開)
  2. 記憶化 bfs 打表儲存所有可達路徑
  3. 優先佇列 periority queue
  4. 多點同時bfs
  5. 反向bfs + bfs 打表儲存所有路徑

stl + 正向bfs

9! 一共有 362880 種可能,10e5 資料規模並不是非常大,所以我考慮用 map<string , bool> 來作為visit。map 的訪問和插入都是 log n
所以對於每一個輸入的序列,時間複雜度都是 n*log n
log 362880 約等於 20 ,所以總的時間複雜度等於 36288020 = 7257600 , 7

1e6,對單個testcase 是可接受的。

如果再考慮 有多個 test case13個testcase 就會達到 1e8 的時間複雜度,顯然是不可接受的。

正向bfs 加 hash判重

此處要引入一個定理 康託展開和逆康託展開

康託展開是一個全排列到一個自然數的雙射,常用於構建hash表時的空間壓縮。設有n個數(1,2,3,4,…,n),可以有組成不同(n!種)的排列組合,康託展開表示的就是是當前排列組合在n個不同元素的全排列中的名次。 (轉自此處)

顯然如果我們此處使用康託展開作 hash 對映,visit 陣列就可以從 一個 map 退化成一個 一維陣列


時間複雜度是 n 同上

但是這麼寫的話,會有很多次重複的操作。所以testcase過大,也會 t。

反向bfs + 記憶化 + hash 判重

由於樹的結構特點,一個子節點一定只有一個父節點。
所以可以直接從根節點開始便利,使用鏈式前向星的思想,對每個節點,記錄該節點的父節點。

然後只需要進行一次bfs 搜尋即可。
程式碼如下:

#include<iostream>
#include<queue>
#include<cstring>
#include<string>
#include<vector>

using namespace std;
struct node
{
    int pre;
    int op;
    string s;
    int x;
    int y;
};
node path[400000];
int vis[400000];

int dir[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
char ss[4] = { 'u','d','l','r'};

int  fac[] = {1,1,2,6,24,120,720,5040,40320,362880}; //i的階乘為fac[i]
int Cantor(string s){
    int sum = 0;
    for(int i = 0; i < 9; i++){
        int num = 0;
        for(int j = i+1; j < 9; j++){
            if(s[j] < s[i])
                num++;//確定當前元素i在未出現的元素中是第幾個(從小到大)
        }
        sum += fac[8-i] * num;
    }
    return sum;
}

void bfs()
{
    int counts =0;
    path[counts].pre = -1;
    path[counts].s = "123456780";
    path[counts].x=2;
    path[counts].y=2;
    queue<int> qu;
    qu.push(counts);
    int visn;

    visn = Cantor(path[counts].s);
    vis[visn] =counts;
    counts++;

    while(!qu.empty())
    {
        int f = qu.front();
        qu.pop();

        int x = path[f].x;
        int y = path[f].y;


        char tp;
        for(int i=0;i<4;i++)
        {
            int px = x+dir[i][0];
            int py = y+dir[i][1];
            if(px<0||px>2||py<0||py>2)continue;

             tp = path[f].s[px*3+py];  // 開始轉換
             path[f].s[px*3+py] = path[f].s[x*3+y];
             path[f].s[x*3+y] = tp;


             visn = Cantor(path[f].s);  //判斷是否出現過
             if(vis[visn]!=-1)
             {
                 tp = path[f].s[px*3+py];  //  回溯
                 path[f].s[px*3+py] = path[f].s[x*3+y];
                 path[f].s[x*3+y] = tp;
                 continue;
             }
             vis[visn] =counts;

             path[counts].op = i;
             path[counts].pre = f;
             path[counts].s = path[f].s;
             path[counts].x = px;
             path[counts].y = py;
             qu.push(counts);
             counts++;




             tp = path[f].s[px*3+py];    //回溯
             path[f].s[px*3+py] = path[f].s[x*3+y];
             path[f].s[x*3+y] = tp;
        }

    }

}

int main()
{
    for(int i=0;i<400000;i++)vis[i]=-1;
    bfs();

    char st1[30];
    string st;
    ios::sync_with_stdio(false);
    vector<int> re;

    while(cin.getline(st1,25))
    {

        st="";
        re.clear();
        for(int i=0;i<17;i+=2)
        {
            if(st1[i]=='x')st = st+'0';
            else st= st+st1[i];
        }
        int viss;
        viss = Cantor(st);
    
        if(vis[viss]!=-1)
        {
             int flag=vis[viss];
             while(path[flag].pre!=-1)
             {
                 cout<<ss[path[flag].op];
                 flag = path[flag].pre;
             }cout<<endl;
        }
         else cout<<"unsolvable"<<endl;

    }
    return 0;
}