八数码难题 洛谷1379

来源:互联网 发布:淘宝联盟认证 编辑:程序博客网 时间:2024/06/05 02:06
洛谷1379八数码难题

题目描述

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入输出格式

输入格式:

输入初试状态,一行九个数字,空格用0表示

输出格式:

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)

输入输出样例

输入样例#1

283104765

输出样例#1

4

 
#include<iostream>#include<cstdio>#include<cstring>#include<map>#include<queue>using namespace std;string st,aim=" 123804765",s,si;char x;map<string,int>p;queue<string>q;queue<int>t;int main(){    cin>>st;    st=' '+st;    q.push(st);    p[st]=1;    t.push(0);    while(!q.empty())      {          s=q.front();          int step=t.front();          t.pop();q.pop();          if(s==aim)            {                cout<<step<<endl;                return 0;          }        si=s;          int pi=si.find("0");          if(pi+3<=9)            {                x=si[pi+3];si[pi+3]=si[pi];si[pi]=x;                if(p[si]==0)                  {                      q.push(si);t.push(step+1);                      p[si]=1;              }          }        si=s;        if(pi-3>=1)          {              x=si[pi-3];si[pi-3]=si[pi];si[pi]=x;                if(p[si]==0)                  {                      q.push(si);t.push(step+1);                      p[si]=1;              }          }        si=s;        if(pi+1<=9&&pi!=3&&pi!=6)          {              x=si[pi+1];si[pi+1]=si[pi];si[pi]=x;                if(p[si]==0)                  {                      q.push(si);t.push(step+1);                      p[si]=1;              }          }        si=s;        if(pi-1>=1&&pi!=7&&pi!=4)          {              x=si[pi-1];si[pi-1]=si[pi];si[pi]=x;                if(p[si]==0)                  {                      q.push(si);t.push(step+1);                      p[si]=1;              }          }      }}
单向宽搜+map判重 1584ms / 26.76MB
/*八个数码可能构成的状态共有9!=362880种情况,可以用宽搜的方法,每种状态用一个九位的int来存储,并确保无重复,需要开bool数组来对各种状态进行标记。于是10^8这样大的数组是空间复杂度难以接受。而如果将八数码的所有状态合看作是一个全排列,每一种状态都是一种排列,就可以用康托展开来压缩所有状态,只需开一个大小为9!=362880的数组来存储是否出现重复情况即可。然后康托展开求阶乘没必要一个一个循环,可以用秦九韶算法4*4!+2*3!+2*2!=113(((4*4+2)*3+2)*2+1)*1=113Have[ ][ ]存储某种状态是否存在Line[ ][ ]存储双向宽搜的状态Last[ ][ ]存储上一种状态的编号,用来输出路径Len[ ]存储双向宽搜的已有状态数Mid[ ]找到的答案Now[ ]存储当前搜索到的八数码的状态Line[0][ ]和line[1][ ]分别为两条队列Line[0][now[0]],Line[1][now[1]]为队列的首元素Line[0][len[0]],Line[1][len[1]]为队列尾元素*/#include<iostream>#include<cstdio>using namespace std;#define MAXN 370000int s[10],line[2][MAXN],len[2],now[2],have[2][MAXN],mid[2],last[2][MAXN],NUM;int p;int turn(){    int result=0;    for(int i=0;i<9;i++){        result*=10;        result+=s[i];    }    return result;}int cantor(){    int a=0,b=0;    for(int i=8;i>=1;i--){//从后往前枚举每个数         int b=s[i];        for(int j=8;j>i;j--){//枚举这个数后面的数             if(s[j]<s[i])--b;         }        a+=b;        a*=i;    }    return a;}void get(int num){    for(int i=8;i>=0;i--){        s[i]=num%10;        if(s[i]==0)p=i;        num/=10;    }}void go(int c,int w){    int temp=0,num=0;    temp=s[p];s[p]=s[p+c];s[p+c]=temp;    ++len[w];    line[w][len[w]]=turn();    num=cantor();    if(have[w][num]!=0)--len[w];    else{        last[w][len[w]]=now[w];        if(have[!w][num]!=0){            mid[w]=len[w];            mid[!w]=have[!w][num];        }        else have[w][num]=len[w];    }    temp=s[p];s[p]=s[p+c];s[p+c]=temp;}void out(){    NUM++;    /*int cnt=0;    for(int i=1;i<=3;i++){        for(int j=1;j<=3;j++){            cout<<s[cnt];cnt++;        }cout<<endl;    }    cout<<endl;*/}void out1(int num){    if(num!=1){        out1(last[0][num]);        //cout<<"\n";    }    get(line[0][num]);    out();}void out2(int num){    get(line[1][num]);    if(num!=mid[1])    out();    if(num!=1){        //cout<<"\n";        out2(last[1][num]);    }}int main(){    char ch[10];    cin>>ch;    for(int i=0;i<9;i++)        s[i]=ch[i]-'0';    int shu=turn();    int kang=cantor();    line[0][1]=shu;//正向bfs的第一个状态是初始状态     len[0]=1;now[0]=1;have[0][kang]=1;    s[0]=1;s[1]=2;s[2]=3;s[3]=8;s[4]=0;s[5]=4;s[6]=7;s[7]=6;s[8]=5;    shu=turn();    kang=cantor();    line[1][1]=shu;//反向bfs的第一个状态是目标状态     len[1]=1;now[1]=1;    if(have[0][kang]==0)have[1][kang]=1;    else{        cout<<0<<endl;    }    while(mid[0]==0&&(now[0]<=len[0]||now[1]<=len[1])){//还没出答案,继续搜         while(mid[0]==0&&len[1]>=len[0]&&now[0]<=len[0]){//反向bfs的状态数多于正向bfs,就着手正向bfs             get(line[0][now[0]]);            //讨论0的位置             if(p>=3&&mid[0]==0)go(-3,0);//在后两行,可以上移             if(p<=5&&mid[0]==0)go(3,0);//在前两行,可以下移             if(mid[0]==0&&p>=1&&(p-1)%3!=2)go(-1,0);//在右两列,可以左移             if(mid[0]==0&&p<=8&&(p+1)%3!=0)go(1,0);//在左两列,可以右移             ++now[0];        }        while(mid[0]==0&&len[0]>=len[1]&&now[1]<=len[1]){            get(line[1][now[1]]);            if(p>=3&&mid[1]==0)go(-3,1);            if(p<=5&&mid[1]==0)go(3,1);            if(mid[1]==0&&p>=1&&(p-1)%3!=2)go(-1,1);            if(mid[1]==0&&p<=8&&(p+1)%3!=0)go(1,1);            ++now[1];        }    }    out1(mid[0]);    out2(mid[1]);    cout<<NUM-1;}
双向宽搜+康托展开 18ms / 24.29MB

 

0 0
原创粉丝点击