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;
}
posted @ 2017-08-26 22:11  LuSimon  阅读(670)  评论(0编辑  收藏  举报