题目链接
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; }
来源:https://www.cnblogs.com/lucianosimon/p/7436528.html