传送门:poj 1077 Eight
题目大意
输入的八数码
将一个八数码最后转换为
1 2 3
4 5 6
7 8 x
的格式,然后打印出路径
康拓展开
如果按照平常的思路,把x的位置看做0,一共有8!个状态,来判断某一个状态是否被访问过。
但是问题来了?怎么把一个带有x的数组转变为转变为数字呢?第一个联想到的就是STL中的map,但是map的速度太慢了,会超时,这个时候我们就要用一种快速的方法把这个带有x的数组转换为数组,那就是hash,如何hash就是用康拓展开这个公式。
这个公式的描述:X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0!
ai表示所有未出现
的元素中比ai这个元素小
的个数,他是最小的就为0.
这个里面有几个关键词就是前面未出现的所有元素
和为出现的元素
;
我们来举个例子
数组 {1,2,3} 按从小到大的排列一共6个:123 132 213 231 312 321。
有一个数组{2,5,7,8}求它的一个排列7528的对应的值
a4=’7’(注意这里的7表示的是元素,不是数字,下同) 这个元素在 前面未出现的所有元素{2,5,7,8}中是前面有几个比它小数?其中2,5都比他小所以a4 = 2;
a3=’5’这个元素在前面未出现的所有元素{2,5,7}(元素’8’已经出现过了,所以不满足红字的条件)中,有一个比它小的数字,所以a3 = 1;
a3=’2’这个元素在前面未出现的所有元素{2,7}中,有0个比它小的数字,所以a3 = 0;
a3=’8’这个元素在前面未出现的所有元素{7}中,有0个比它小的数字,所以a3 = 1;
所以X = a4*(4-1)!+a3*(3-1)!+a2*(2-1)!+a1*(1-1)!;
X = 2*(3!) + 1*(2!) +0*(1!) + 0*(0!);
= 14
上面就是康拓展开的一般过程,简单描述为已知一个排列,就能知道这个排列中元素的个数len,我们可以通过这几个元素和len将这个排列转换为一个十进制数
上面所说的是康拓展开能解决的问题!
康拓逆展开生成全排列
既然我们可以通过排列把给出的元素转换为一个十进制数,那么肯定能通过这个这个逆向的过程,由X计算出a1,a2,a3….an。
我们通过一个实例来解释上面所说的话,就比如上面那个解释康拓的例子,我们是否能过通过知道X=14以及元素数组{2,5,7,8}计算出a1,a2,a3,a4呢?
在不考虑任何范围的情况下,列举不完全的下面几种情况:
14 = 1*(3!) + 1*(2!) +1*(1!) + 1*(0!);
14 = 2*(3!) + 2*(2!) +0*(1!) + 0*(0!);
14 = 0*(3!) + 6*(2!) +1*(1!) + 1*(0!);
满足情况的之有第二种情况因为最后一个数也就是a1必须是0,因为做最后只剩下一个数了,不可能再有比他小的数字了
我们就用这种情况14 = 2*(3!) + 2*(2!) +0*(1!) + 0*(0!)
,来推算出一般的情况,
1. 因为最后三个数加起来是不可能超过(3!)的,所以我们可以通过14%3!来得到a4
2. 用14/3!的余数就是剩下的后面三个数之和,为4
3. 然后通过4在对2!去模,就得到了a3
4. 然后一直重复的循