POJ 3567 Eight II (八数码问题+bfs+康托展开)

题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=3567

解题思路

这道题是POJ 1077 Eight的升级版, 区别在于POJ1077的终点是确定的,那么其他情况都是可以由这一种情况推出. 这道题的起点和终点似乎都是不确定的,如果暴力搜索的话一定会超时,这就很难办. 可以从另一个角度来思考, 我们根据起点中'X'的位置来进行分类,而其他位置的棋子用其编号(1~8)来表示即可,这样把起点从\(9!\)中情况缩小到了9种情况,以这9种情况事先进行一个搜索就可以了.
输出路径要求是字典最小序,以d, l, r, u的顺序搜索即可

搜索思路

这道题的另一个难点----也是八数码问题的核心----在于如何标识每一种情况, 方法是利用康托展开算法进行哈希

康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
康托展开的转换公式: \(X=a_n(n-1)!+a_{n-1}(n-2)!+\cdots+a_1\cdot0!\),其中\(a_i\)为整数,并且\(0\leq a_i<i,1\leq i\leq n\)
对公式的解释: 设\(m_i\)是数组中从后往前数第i个数, 则\(a_i\)\(m_i\)后面的数中比\(m_i\)小的数的个数
举例:3 5 7 4 1 2 9 6 8 展开为 98884。因为X=2*8!+3*7!+4*6!+2*5!+0*4!+0*3!+2*2!+0*1!+0*0!=98884.
解释:
排列的第一位是3,比3小的数有两个,以这样的数开始的排列有8!个,因此第一项为2*8!
排列的第二位是5,比5小的数有1、2、3、4,由于3已经出现,因此共有3个比5小的数,这样的排列有7!个,因此第二项为3*7!
以此类推,直至0*0!
----维基百科

AC代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <queue>
#include <stack>

using namespace std;

const int maxn = 4e5;

int a[10];
int cnt[10];
int tmp[9];
stack<char> ans;
char sp[15], ep[15];
int pos, tar[9];
int mp[9][9] = {//9代表'X'
    9, 1, 2, 3, 4, 5, 6, 7, 8,
    1, 9, 2, 3, 4, 5, 6, 7, 8,
    1, 2, 9, 3, 4, 5, 6, 7, 8,
    1, 2, 3, 9, 4, 5, 6, 7, 8,
    1, 2, 3, 4, 9, 5, 6, 7, 8,
    1, 2, 3, 4, 5, 9, 6, 7, 8,
    1, 2, 3, 4, 5, 6, 9, 7, 8,
    1, 2, 3, 4, 5, 6, 7, 9, 8,
    1, 2, 3, 4, 5, 6, 7, 8, 9
};

struct node{
    int num[9];
    int pos;
    int pre;
    char path;
    node(): pos(-1), path(0){}
}que[9][maxn];

int cant(int *ori){    //康托展开
    for (int i = 1; i < 10; ++i){
        cnt[i] = i - 1;
    }
    int s = 0;
    for (int i = 0; i < 9; ++i){
        s += cnt[ori[i]] * a[8 - i];
        for (int j = ori[i]; j < 10; ++j){
            --cnt[j];
        }
    }
    return s;
}

void bfs(const int p, int rt){
    queue<int> q;
    q.push(rt);
    int ct, frt;
    node * cur;
    while (!q.empty()){
        frt = q.front();
        q.pop();
        cur = &que[p][frt];
        memcpy(tmp, cur->num, sizeof tmp);
        //0: down
        if (cur->pos < 6){
            swap(tmp[cur->pos], tmp[cur->pos + 3]);
            ct = cant(tmp);
            if (que[p][ct].pos == -1){
                que[p][ct].pos = cur->pos + 3;
                memcpy(que[p][ct].num, tmp, sizeof tmp);
                que[p][ct].pre = frt;
                que[p][ct].path = 'd';
                q.push(ct);
            }
            swap(tmp[cur->pos], tmp[cur->pos + 3]);
        }
        //1: left
        if (cur->pos % 3 != 0){
            swap(tmp[cur->pos], tmp[cur->pos - 1]);
            ct = cant(tmp);
            if (que[p][ct].pos == -1){
                que[p][ct].pos = cur->pos - 1;
                memcpy(que[p][ct].num, tmp, sizeof tmp);
                que[p][ct].pre = frt;
                que[p][ct].path = 'l';
                q.push(ct);
            }
            swap(tmp[cur->pos], tmp[cur->pos - 1]);
        }
        //2: right
        if (cur->pos % 3 != 2){
            swap(tmp[cur->pos], tmp[cur->pos + 1]);
            ct = cant(tmp);
            if (que[p][ct].pos == -1){
                que[p][ct].pos = cur->pos + 1;
                memcpy(que[p][ct].num, tmp, sizeof tmp);
                que[p][ct].pre = frt;
                que[p][ct].path = 'r';
                q.push(ct);
            }
            swap(tmp[cur->pos], tmp[cur->pos + 1]);
        }
        //3: up
        if (cur->pos >= 3){
            swap(tmp[cur->pos], tmp[cur->pos - 3]);
            ct = cant(tmp);
            if (que[p][ct].pos == -1){
                que[p][ct].pos = cur->pos - 3;
                memcpy(que[p][ct].num, tmp, sizeof tmp);
                que[p][ct].pre = frt;
                que[p][ct].path = 'u';
                q.push(ct);
            }
            swap(tmp[cur->pos], tmp[cur->pos - 3]);
        }
    }
}

int main(){
    a[0] = 1;
    for (int i = 1; i < 10; ++i){
        a[i] = i * a[i - 1];
    }
    for (int i = 0; i < 9; ++i){
        int ct = cant(mp[i]);
        que[i][ct].pos = i;
        memcpy(que[i][ct].num, mp[i], sizeof que[i][ct].num);
        bfs(i, ct);
    }
    int cs;
    scanf("%d", &cs);
    for (int t = 1; t <= cs; ++t){
        scanf("%s", sp);
        scanf("%s", ep);
        int p;//'X'的位置
        for (p = 0; ; ++p){
            if (sp[p] == 'X'){
                break;
            }
        }
        int tp;
        for (tp = 0; ; ++tp){
            if (ep[tp] == 'X'){
                break;
            }
        }

                //用棋子的编号来标识终点
        for (int i = 0; i < 9; ++i){
            for (int j = 0; j < 9; ++j){
                if (ep[i] == sp[j]){
                    tar[i] = mp[p][j];
                }
            }
        }
        int ct = cant(tar);

        while (!ans.empty()) ans.pop();
        while (que[p][ct].path){
            ans.push(que[p][ct].path);
            ct = que[p][ct].pre;
        }
        printf("Case %d: %d\n", t, ans.size());
        while (!ans.empty()){
            printf("%c", ans.top());
            ans.pop();
        }
        printf("\n");
    }
    
    return 0;
}
发布了10 篇原创文章 · 获赞 3 · 访问量 867

猜你喜欢

转载自blog.csdn.net/ErrorNam/article/details/82976371