poj 1077 Eight 八数码问题( 康拓展开+BFS状态压缩)

来源:互联网 发布:windows 10中文包 编辑:程序博客网 时间:2024/05/18 00:37

传送门: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. 然后一直重复的循环1,2过程就能过依次得到an,an-1…..a1
上面的过程也叫做辗转相除

通过上面的过程就能通过数组元素An和对应的十进制数,就能求出这个排列

解题思路

懂得了上面的理论,我们就知道可以把x的位置看做0,然后将这个数值数组通过康拓展开为一个十进制数,表示初始状态,然后一共过9!个状态,通过BFS枚举这些状态,求得转化的路径。
将一个数组转换为十进制数的代码为:

int fac[]={1, 1, 2, 6,24, 120,720,5040,40320,362880};//         0!,1!,2!,3!,4!,5! ,6! ,7!  , 8!  , 9!int Cantor(int s[]){    int sum = 0;    for(int i=0;i<9;i++)    {        int cnt = 0;        for(int j=i+1;j<9;j++)            if(s[i] > s[j])                cnt++;        sum += (cnt*fac[8-i]);    }    return sum + 1;}

然后就是一般的BFS问题了

AC代码

#include<cstdio>#include<cstring>#include<set>#include<stack>#include<cmath>#include<cstring>#include<string>#include<algorithm>#include<queue>#include<cstdlib>#include<iostream>#include<map>using namespace std;const int MAXN = 900005;typedef long long LL;  //lldint fac[]={1, 1, 2, 6,24, 120,720,5040,40320,362880};//         0!,1!,2!,3!,4!,5! ,6! ,7!  , 8!  , 9!string path;int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};//u,d,l,rchar indexs[5]="udlr";int aim=46234;bool vis[MAXN];struct Node {    int loc;  //x的位置    int s[10];//3*3元素    string path;//路径    int hashVal;//当前状态对应的hash值}chess;int Cantor(int s[]){    int sum = 0;    for(int i=0;i<9;i++)    {        int cnt = 0;        for(int j=i+1;j<9;j++)            if(s[i] > s[j])                cnt++;        sum += (cnt*fac[8-i]);    }    return sum + 1;}bool BFS(){    queue<Node>q;    Node cur;    Node next;    q.push(chess);    vis[chess.hashVal] = true;    while(!q.empty())    {        cur = q.front();        q.pop();        //cout<<cur.loc<<endl;        if(cur.hashVal == aim)        {            path = cur.path;            return true;        }        int x = cur.loc/3;        int y = cur.loc%3;        for(int i=0;i<4;i++)        {            int tempX = x + dir[i][0];            int tempY = y + dir[i][1];            if(tempX<0 || tempX>2 || tempY<0 || tempY>2 )                continue;            next = cur;            next.loc = 3*tempX + tempY;            //下面两行代码要认真地看一下,实现了x位置和他相邻位置的转换            //1.先把将要是x位置中的数放到以前是x的位置             next.s[cur.loc] = next.s[next.loc];            //2.即将是x的位置转换为x            next.s[next.loc] = 0;            next.hashVal = Cantor(next.s);            if(next.hashVal == aim)            {                next.path = cur.path + indexs[i];                path = next.path;                return true;            }            if(vis[next.hashVal])                continue;            next.path = cur.path + indexs[i];            vis[next.hashVal] = true;            q.push(next);        }    }    return false;}int main(){    char ch[10];    memset(vis,false,sizeof vis);    for(int i=0;i<9;i++)    {        scanf("%s",ch);        if(ch[0] == 'x')        {            chess.s[i] = 0 ;            chess.loc = i;        }        else        {            chess.s[i] = ch[0] - '0';        }    }    chess.hashVal = Cantor(chess.s);    bool flag = BFS();    if(flag)        cout<<path<<endl;    else        printf("unsolvable\n");    return 0;}
1 0
原创粉丝点击