HOJ 1868 八数码 双广搜

用《算法竞赛入门经典》里的两种判重映射都超时,最后网上搜才知道,数据严的时候应该用双向BFS

po下网上找的代码

#include <iostream>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <math.h>  
#include <queue>  
#include <algorithm>  
using namespace std;  
  
#define Maxn 400000  
#define Maxm 400000  
struct State  
{  
    int a[9];  
    int zero;  
    int g;  
    State()  
    {  
    }  
    State(int b[9])  
    {  
        for(int i=0; i<9; i++)  
        {  
            a[i]=b[i];  
            if(b[i]==0) zero=i;  
        }  
        g=0;  
    }  
};  
  
int dist[2][Maxn];  
int fact[9];  
int vis[2][Maxm];  
State first,last;  
  
int dx[] = {-1,1,0,0};  
int dy[] = {0,0,-1,1};  
  
queue<State> q1,q2;  
  
void init_lookup_table()  
{  
    fact[0] = 1;  
    for(int i=1; i<9; i++) fact[i] = fact[i-1] * i;  
}  
bool same(State x,State y)  
{  
    for(int i=0; i<9; i++)  
    {  
        if(x.a[i]!= y.a[i]) return false;  
    }  
    return true;  
}  
int getcode(State x)  
{  
    int code=0;  
    for(int i=0; i<9; i++)  
    {  
        int cnt=0;  
        for(int j=i+1; j<9; j++)  
        {  
            if(x.a[i]>x.a[j]) cnt++;  
        }  
        code+=fact[8-i]*cnt;  
    }  
    return code;  
}  
int try_to_insert(State x,int kind)  
{  
    int code = getcode(x);  
    if(vis[kind][code]) return 0;  
    dist[kind][code] = x.g;  
    return vis[kind][code] = 1;  
}  
  
int bfs()  
{  
    int code;  
    int x,y,newx,newy,z,newz,d;  
    memset(dist,0,sizeof(dist));  
    memset(vis,0,sizeof(vis));  
    while(!q1.empty()) q1.pop();  
    while(!q2.empty()) q2.pop();  
    q1.push(first);  
    q2.push(last);  
    try_to_insert(first,0);  
    try_to_insert(last,1);  
    while(!q1.empty() || !q2.empty())  
    {  
        int n1 = q1.size();  
        int n2 = q2.size();  
        //处理环A  
        if(n1 < n2 || n2 == 0)  
        {  
            //一层扩展  
            for(int i=0; i<n1; i++)  
            {  
                State s = q1.front();  
                q1.pop();  
                code = getcode(s);  
                //在对方这个状态是否存在  
                if(vis[1][code]) return dist[0][code] + dist[1][code];  
                z = s.zero;  
                x = z/3;  
                y = z%3;  
                for(d=0; d<4; d++)  
                {  
                    newx = x + dx[d];  
                    newy = y + dy[d];  
                    newz = newx*3 + newy;  
                    if(newx>=0 && newx <3 && newy>=0 && newy<3)  
                    {  
                        State t = s;  
                        t.a[newz] = s.a[z];  
                        t.a[z] = s.a[newz];  
                        t.g = s.g + 1;  
                        t.zero = newz;  
                        if(try_to_insert(t,0)) q1.push(t);  
                    }  
                }  
            }  
        }  
        //处理环B  
        else  
        {  
            //一层扩展  
            for(int i=0; i<n2; i++)  
            {  
                State s = q2.front();  
                q2.pop();  
                code = getcode(s);  
                //在对方这个状态是否存在  
                if(vis[0][code]) return dist[0][code] + dist[1][code];  
                z = s.zero;  
                x = z/3;  
                y = z%3;  
                for(d=0; d<4; d++)  
                {  
                    newx = x + dx[d];  
                    newy = y + dy[d];  
                    newz = newx*3 + newy;  
                    if(newx>=0 && newx <3 && newy>=0 && newy<3)  
                    {  
                        State t = s;  
                        t.a[newz] = s.a[z];  
                        t.a[z] = s.a[newz];  
                        t.g = s.g + 1;  
                        t.zero = newz;  
                        if(try_to_insert(t,1)) q2.push(t);  
                    }  
                }  
            }  
        }  
    }  
    return 0;  
}  
  
//求逆序数对,要排除第二个数字是0的那种情况  
int reversePair(State s)  
{  
    int ans = 0;  
    for(int i=0; i<9; i++)  
    {  
        for(int j=i+1; j<9; j++) if(s.a[i]>s.a[j] && s.a[j]!=0) ans++;  
    }  
    return ans;  
}  
bool preJudge()  
{  
    if(same(first,last))  
    {  
        puts("0");  
        return false;  
    }  
    else if((reversePair(first) + reversePair(last))%2)  
    {  
        puts("-1");  
        return false;  
    }  
    return true;  
}  
int main()  
{  
#ifndef ONLINE_JUDGE  
    freopen("in.txt","r",stdin);  
#endif  
    int t;  
    int ans;  
    int begin[9],end[9];  
    scanf(" %d",&t);  
    init_lookup_table();  
    while(t--)  
    {  
        for(int i=0; i<9; i++) scanf(" %d",&begin[i]);  
        State a(begin);  
        first = a;  
        for(int i=0; i<9; i++) scanf(" %d",&end[i]);  
        State b(end);  
        last = b;  
        if(preJudge())  
        {  
            ans = bfs();  
            printf("%d\n",ans);  
        }  
    }  
    return 0;  
}  
映射方法依然是书中的第一种,我不知道这叫什么,AC代码的博主叫它全排列映射。

其他都很好理解,唯独其中判断是否可达的条件即:

 起点和终点的逆序奇偶性是否相同?不同则不可达。

也有人总结了简易版的证明且比较清晰:

为了方便讨论,我们把它写成一维 的形式,并以0代替空格 位置。那么表示如下:

1 2 3 4 5 6 7 8 0

通过实验得知,以下状态是无解的(交换了前两个数字1 2):

2 1 3 4 5 6 7 8 0

八数码问题的有解无解的结论:

一个状态表示成一维的形式,求出除0之外所有数字的逆序数之和,也就是每个数字前面比它大的数字的个数的和,称为这个状态的逆序。

若两个状态的逆序奇偶性 相同,则可相互到达,否则不可相互到达。

由于原始状态的逆序为0(偶数),则逆序为偶数的状态有解。

也就是说,逆序的奇偶将所有的状态分为了两个等价类 ,同一个等价类中的状态都可相互到达。

简要说明一下: 

当左右移动空格时,逆序不变。当上下移动空格时,相当于将一个数字向前(或向后)移动两格,跳过 的这两个数字要么都比它大(小),

逆序可能±2;要么一个较大一个较小,逆序不变

如 1 2 3 1 2 3 | | 1 2 3 1 2 3

4 5 6 -----> 4 5 0 前者逆序数0 后者为2 | | 4 7 6 -----> 4 7 0 前者为3 后者为3

7 8 0 7 8 6 | | 5 8 0 5 8 6



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值