康托展开是将n个数的全排列映射到自然数空间{0, 1, ... , n!-1}的双射。在介绍康托展开之前,先介绍几个概念:变进制数、逆序对。
我们经常使用进制有:二进制、十进制、十六进制。这些进制称为“常数进制”,有一个共同点,即逢p进1;比如,十六进制是每位逢16进1,十进制数每位逢10进1。p进制数K可表示为
有这样一种变进制数:第1位逢2进1,第2位逢3进1,……,第n位逢n+1进1。变进制数可K表示为
这种变进制数K有如下性质(证明参看[1]):
(1)当所有位ai均为i时,K有最大值(n+1)!-1
(2)当所有位ai均为0时,K有最小值0
对有 n 个互异元素的有序集A,如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。
例如:数组 <2,3,8,6,1> 的逆序对为:<2,1> <3,1> <8,6> <8,1> <6,1> 共5个。
假设我们有b0,b1,b2,b3,...,bn共n+1个不同的元素,并假设各元素之间有一种次序关系 b0<b1<b2<...<bn。对它们进行全排列,共产生(n+1)!种不同的排列。对于产生的任一排列,第i个元素(1 <= i <= n)与它前面的i个元素构成的逆序对的对数为di(0 <= di <= i),那么我们得到一个逆序对对数序列d1,d2,...,dn(0 <= di <= i)。这不就是前面的n位变进制数的每一位么?于是,我们用n位变进制数M来表示该排列:
[1] tyc611, 一种变进制数及其应用(全排列之Hash实现).
[2] 维基百科, 逆序对.
[3] 维基百科, 康托展开.
n数码问题:输入一个排列,求能得到目标排列的最少步数变换。
先将排列映射到自然数空间,然后用BFS遍历求解最少步数的策略(参看前一篇)。
用C++结果TLE,换成G++结果报错'memset' was not declared in this scope,最后加上#include <cstring> #include <cstdio>通过。
源代码:
1077 | Accepted | 6056K | 219MS | G++ | 2001B | 2013-12-06 10:28:46 |
#include<iostream> #include <queue> #include <cstring> #include <cstdio> using namespace std; #define MAX 362880 const int factorial[9] = {1,1,2,6,24,120,720,5040,40320}; //阶乘 int end,visit[MAX],solvable; struct _parent //记录父节点及移动方向 { int key; char op; }parent[MAX]; struct state //xtile记录9(即x)所在位置 { int per[9],key,xtile; }; state start; queue<state>que; int hash(int per[]) //康托展开,即全排列的hash { int i,j,count,result=0; for(i=1;i<9;i++) { count=0; for(j=0;j<i;j++) if(per[j]>per[i]) count++; result+=count*factorial[i]; } return result; } void swap(int *a,int *b) { int temp; temp=*a; *a=*b; *b=temp; } void exchange(state *ptr,char op,int varia) //交换元素9 { int i; state next; for(i=0;i<9;i++) next.per[i]=ptr->per[i]; swap(next.per[ptr->xtile],next.per[ptr->xtile+varia]); next.key=hash(next.per); next.xtile=ptr->xtile+varia; if(!visit[next.key]) { visit[next.key]=1; parent[next.key].key=ptr->key; parent[next.key].op=op; que.push(next); } } void bfs() { int que_size; state head; memset(visit,0,sizeof(visit)); solvable=0; visit[start.key]=1; que.push(start); while(!que.empty()&&!solvable) { que_size=que.size(); while(que_size--) { head=que.front(); que.pop(); if(head.key==end) { solvable=1; return; } if(head.xtile/3!=0) exchange(&head,'u',-3); if(head.xtile/3!=2) exchange(&head,'d',+3); if(head.xtile%3!=2) exchange(&head,'r',+1); if(head.xtile%3!=0) exchange(&head,'l',-1); } } } void init() { int i; char ch; for(i=0;i<9;i++) { cin>>ch; if(ch=='x') { start.per[i]=9; start.xtile=i; } else start.per[i]=ch-'0'; } start.key=hash(start.per); end=0; } void output(int k) { if(k!=start.key) { output(parent[k].key); printf("%c",parent[k].op); } } int main() { init(); bfs(); if(solvable) { output(end); printf("\n"); } else printf("unsolvable\n"); return 0; }