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

题目概述

给出一个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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值