【浅谈康托展开】HDU1043[Eight]题解

来源:互联网 发布:布尔教育java怎么样 编辑:程序博客网 时间:2024/06/05 03:39

题目概述

给出一个3*3的矩阵,其中有一个格子是空格,其他都是数字,每次移动可以将格子附近的数字移到格子中,同时原先的数字变成格子。给定一个初始状态和最终状态,求一个方案。

解题报告

显然状态顶多只有9!=362880个,所以我们就会想到Bfs。但是如何判断一个状态是否出现过呢?用set肯定是可以的,但效率不是太高(常数也大),用hash也是可以的,然而完美的hash函数是存不下来的(当成9进制数)。这里就有一个比较好用的东西叫康托展开,其实也是hash,但是hash函数不但是完美的,而且空间开销也达到理论下限:9!=362880,非常适合储存全排列。

其实康托展开并不难理解,以274563018(下面称为当前排列)为例:
第一位为2,那么第一位为0,1的所有排列都比当前排列小,总计2*8!个。
第一位为2,第二位为7,那么第二位为0,1,3,4,5,6(没有2,第一位用过了)的所有排列都比当前排列小,总计6*7!个。
第一位为2,第二位为7,第三位为4,那么第三位为0,1,3的所有排列都比当前排列小,总计3*6!个。
……
所以,假设排列有n位,a[i]表示i+1~n中比i位上的数小的数的个数,那么比当前排列小的排列总个数就等于:
a[1](n1)!+a[2](n2)!+a[3](n3)!++a[n]0!

康托展开还有逆运算,就是已知有多少个排列比当前排列小,求当前排列。以208745为例,过程非常类似转进制:
208745/(8!)=5,说明有5个数比第一位小,即0,1,2,3,4,所以第一位为5。然后208745%(8!)=7145。
7145/(7!)=1,说明有1个数比第二位小,即0,所以第二位为1。然后7145%(7!)=2105。
2105/(6!)=2,说明有2个数比第三位小,即0,2(没有1,第二位用过了),所以第二位为3,然后2105%(6!)=665。
……

有了康托展开和逆运算,这道题就迎刃而解了……才怪!我写完了Bfs之后狂TLE不止,怎么改都超时。然后当我A了POJ之后我发现HDU多组数组我才疯狂TLE,于是想到了预处理。所以我从最终状态开始遍历,预处理了所有起始状态,然后从TLE变成了156ms……

示例程序

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int maxn=362880;const int fac[9]={1,1,2,6,24,120,720,5040,40320};const int fl[4]={-1,1,-3,3};const int lst[9]={1,2,3,4,5,6,7,8,0};const char op[4]={'l','r','u','d'};struct data {int x,fa;char ch;};int gl,who[maxn+5],fst[9];data que[maxn+5];bool vis[maxn+5];void Travel(int now){    if (now==1) return;    putchar(que[now].ch);Travel(que[now].fa);}bool check(int x,char ch){    if (x<0||x>8) return false;    if (ch=='l'&&x%3==2) return false;    if (ch=='r'&&x%3==0) return false;    return true;}int Contor(const int *a) //康托展开{    int pos=0;    for (int i=0;i<=8;i++)    {        int tot=0;        for (int j=i+1;j<=8;j++) tot+=a[i]>a[j]; //有tot个数比a[i]小        pos+=tot*fac[9-i-1];    }    return pos+1;}void INV_Contor(int pos,int *a) //康托展开逆运算{    bool vis[9];pos--;memset(vis,0,sizeof(vis));    for (int i=0;i<=8;i++)    {        int now=pos/fac[9-i-1],j;        for (j=0;j<=8;j++) if (!vis[j])            if (now==0) break; else now--; //第now个出现的就是第i位        a[i]=j;vis[j]=true; //j出现过了,标记        pos%=fac[9-i-1];    }}int getp(int *a) {for (int i=0;i<=8;i++) if (!a[i]) return i;}bool Bfs(){    memset(vis,0,sizeof(vis));    int Head=0,Tail=0;que[++Tail]=(data){Contor(lst),0,0};    while (Head!=Tail)    {        int x[9],now[9],p;        INV_Contor(que[++Head].x,x);p=getp(x); //逆运算求当前状态x        for (int i=0;i<=3;i++)            if (check(p+fl[i],op[i]))            {                memcpy(now,x,sizeof(now));swap(now[p],now[p+fl[i]]);                int pos=Contor(now); //用康托展开将now变为数字                if (!vis[pos])                {                    que[++Tail]=(data){pos,Head,op[i^1]}; //反向处理,所以是op[i^1]                    vis[pos]=true;who[pos]=Tail;                }               }    }    return false;}char getrch(){    char ch=getchar();    while (('9'<ch||ch<'0')&&ch!='x')    {        if (ch==EOF) return EOF;        ch=getchar();    }    if (ch=='x') return '0';    return ch;}int main(){    freopen("program.in","r",stdin);    freopen("program.out","w",stdout);    Bfs();char ch;    while ((ch=getrch())!=EOF)    {        fst[0]=ch-48;for (int i=1;i<=8;i++) fst[i]=getrch()-48;        int pos=Contor(fst);        if (!who[pos]) printf("unsolvable"); else Travel(who[pos]);        putchar('\n');    }    return 0;}