[BFS+康托展开]Hdu 1043 Eight

题目传送门

这道题就是我们耳熟能详的八数码问题,如果写BFS必然涉及到如何判断一个状态是否进队,可以把序列表示成一个9进制的数,用set或者直接hash,但是这样操作效率过低。0~8的排列最多只有9!种排列方式,所以如果我们能很快求出每个序列的大小,那么直接开vis标记就可以了。所以引进了康托展开。

康托展开

假设我们拿到一个序列是32451,求这个序列的在所有排列中的次序。
其实我们只要确定有几个排列比当前序列小就可以了。
先看第一位,显然以1,2开头的序列都比当前序列小,所以有2*4!种方案。
第一位确定为3后,看第二位,显然有1*3!种方案。
前两位为32,看第三位,因为32确定,所以只有1比4小,有1*2!种方案。
第4位有1*1!种,最后一位没有比1更小的方案了。
所以有2*4!+1*3!+1*2!+1*1!+0*0!=57比当前序列小。
通过上面的讨论可以发现:

X=(a[n]1)(n1)!+(a[n1]1)(n2)!+...+(a[2]1)1!+(a[1]1)0!

其中a[i]为第i个数在当前未出现的元素中是排在第几个。
显然最后答案要返回x+1。

康托逆展开

虽然这道题可以不用康托的逆展开,但是还是要介绍一下这个非常有用的逆展开,就是给定次序求序列,不难发现就是上述给出公式中给出X然后求a[1]~a[n]。还是拿57举例吧,n=5。
先把X/(n-1)!=2,那么(a[n]-1)=2,显然等于3,X=X%(n-1)!=9。
然后继续X/(n-2)!=1,a[n-1]等于2,X=X%(n-2)!=3。
X/(n-3)!=1,虽然a[n-3]排在第二位但是前面有2,3出现过所以是a[n-3]=4,X=1。
继续以上方法……
最后可以得出原序列就是32451。

解题思路

从末状态开始bfs,倒存路径之后输出。

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=362885,ji[9]={1,1,2,6,24,120,720,5040,40320};
const int flg[4]={-1,1,-3,3};
const char op[4]={'l','r','u','d'};
struct jz{
    int x,f,a[9];
    int contor()const{
        int num=0;
        for (int i=0;i<9;i++){
            int tot=0;
            for (int j=0;j<i;j++) tot+=a[i]>a[j];
            num+=(a[i]-tot)*ji[9-i-1];
        }
        return num+1;
    }
    char ch;
}que[maxn],b;
bool vis[maxn];
int hed,til,who[maxn];
bool check(int x,int y){
    if (x<0||x>8) return 0;
    if (x%3==2&&y==-1) return 0;
    if (x%3==0&&y==1) return 0;
    return 1;
}
void bfs(){
    int x,y;til=1;
    for (int i=0;i<8;i++) que[1].a[i]=i+1;que[1].a[8]=0;
    que[1].x=8;x=que[1].contor();who[x]=1;
    while (hed!=til){
        jz now=que[++hed];x=now.x;
        for (int i=0;i<4;i++)
        if (check(x+flg[i],flg[i])){
            now=que[hed];swap(now.a[x+flg[i]],now.a[x]);
            now.f=hed;now.x+=flg[i];now.ch=op[i^1];y=now.contor();
            if (!vis[y]){que[++til]=now;vis[y]=1;who[y]=til;}
        }
    }
}
void DFS(int x){
    if (x==1) return;
    putchar(que[x].ch);DFS(que[x].f);
}
inline char _read(){
    char ch=getchar();
    while ((ch<'1'||ch>'8')&&ch!='x'&&ch!=EOF) ch=getchar();
    if (ch=='x') return '0';else return ch;
}
int main(){
    freopen("exam.in","r",stdin);
    freopen("exam.out","w",stdout);
    bfs();char c=_read();
    while(c!=EOF){
        b.a[0]=c-48;for (int i=1;i<9;i++) b.a[i]=_read()-48;
        int y=b.contor();
        if (who[y]) DFS(who[y]);else printf("unsolvable");
        printf("\n");c=_read();
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值