八數碼問題(A*搜索+剪枝)

題目
新技能!
康託展開:個人感覺就是全排列中用哈希,把狀態壓縮了,例如1~9的全排列,如果直接保存,那麼最大就是987654321,如果需要標記這個狀態的話,數組是不可行的,採用康託展開就可將範圍壓縮到1 ~ 9!,這樣就可以開數組標記;其實也就是離散化,但是離散化的查找是log的,而康託展開可以O(1)地求得;
公式:X = A[0] * (n-1)! + A[1] * (n-2)! + … + A[n-1] * 0!
A[i]代表比第i位的數字小並且前面沒有出現過的數字個數;
具體舉例:考慮排列3421,我們要求得有多少個排列小於等於它
對於第一位3:這一位取2或者1的話,那麼後面三位怎麼排都小於,所以這裏有2 * 3!種
如果第一位已經取定是3,考慮第二位:比這一位小的有1,2,3,但是3在前面已經用過了,於是只有兩種取法,所以這一位就是 22! 種
前兩位取定,考慮第三位:只有1比它小,所以這裏增加了 1
1!種;
最後一位是0
再加上自己所以總共就是:
2 * 3!+22! +11!+0+1 = 18種; Over
這題呢,方法好多,可以bfs打表,也可以雙向bfs,還可以A搜索,但是最重要的就是必須要剪枝,也就是單獨判斷不可行的情況,這個可以根據逆序奇偶來判斷,在移動的過程中,不看9的話左右移動逆序數奇偶不變,上下移動改變2或者0,所以這個過程中逆序數奇偶不變。
貼上A
搜索

#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<map>
#include<cstring>
#include<queue>
const int maxn = 4e5;
const int maxm = 1e6 + 10;
const int inf_max = 0x3f3f3f;
using namespace std;
typedef long long ll;
int vis[maxn],fac[10];
int dx[] = {0,0,1,-1};//右,左,下,上
int dy[] = {1,-1,0,0};
char ans[maxn],dir[] = {'r','l','d','u'};
pair<char,int>pre[maxn];
struct status {
    int f,g;  //f是估計代價
    char s[10];
    bool operator<(const status &a)const {
        return g+f > a.g + a.f;
    }
};
int kt(char *s) {
    int l = strlen(s),ret = 1;
    for(int i = 0;i < l ;++i) {
        int cnt = 0;
        for(int j = i + 1;j < l; ++j)
            cnt += (s[j] < s[i]);
        ret += fac[l - i - 1] * cnt;
    }
    return ret;
}
int getf(char *s) {
    int ret = 0;
    for(int i = 0;i < 9; ++i) {
        int now = s[i] - '0' - 1;
        if(now == 8) continue;
        ret += (abs(i/3 - now/3) + abs(i%3 - now%3));
    }
    return ret;
}
bool check(int x,int y) {
    return (x >= 0 && x < 3 && y >= 0 && y < 3);
}
bool bfs(char *s) {
    memset(vis,0,sizeof(vis));
    priority_queue<status>q;
    while(!q.empty()) q.pop();
    status cur;cur.f = getf(s);cur.g = 0;
    int hs = kt(s);strcpy(cur.s,s);pre[hs].second = -1;
    q.push(cur);
    while(!q.empty()) {
        cur = q.top();q.pop();
        int hs_cur = kt(cur.s),pos;
        //printf("%s \n",cur.s);
        if(hs_cur == 1) return true;
        vis[hs_cur] = 1;
        for(int i = 0;i < 9; ++i) if(cur.s[i] == '9') pos = i;
        for(int i = 0;i < 4; ++i) {
            status tmp = cur;
            int tx = pos/3 + dx[i],ty = pos%3 + dy[i];
            if(!check(tx,ty)) continue;
            int tt = tx*3 + ty;
            tmp.s[pos] = tmp.s[tt],tmp.s[tt] = '9';
            int hs_tmp = kt(tmp.s);
            if(vis[hs_tmp]) continue;
            tmp.g++;tmp.f = getf(tmp.s);pre[hs_tmp].second = hs_cur,pre[hs_tmp].first = dir[i];
            q.push(tmp);
        }
    }
    return false;
}
void print() {
    int i = 1,cnt = 0;
    while(pre[i].second != -1) {
        ans[cnt++] = pre[i].first;
        i = pre[i].second;
    }
    ans[cnt] = '\0';
    for(i = cnt - 1; i >= 0; --i) printf("%c",ans[i]);
    cout<<endl;
}
int main()
{
    fac[0] = 1;
    for(int i = 1;i < 10; ++i) {
        fac[i] = fac[i - 1] * i;
    }
    char str[50];
    while(gets(str)) {
        int len = strlen(str),pos = 0;
        for(int i = 0;i < len; ++i) {
            if(str[i] == 'x') {
                str[pos++] = '9';
            }else if(str[i] >= '0' && str[i] <= '9') {
                str[pos++] = str[i];
            }
        }
        str[pos] = '\0';
        int ni = 0;
        for(int i = 0;i < 9; ++i) {
            if(str[i] == '9') continue;
            for(int j = i - 1;j >= 0; --j) {
                if(str[j] == '9') continue;
                if(str[i] < str[j]) ni++;
            }
        }
        if(ni & 1) {
            puts("unsolvable");
        }else {
            bfs(str);print();
        }
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章