HDU1043(Eight)

Eight

题目传送门
在这里插入图片描述
Input

You will receive, several descriptions of configuration of the 8 puzzle. One description is just a list of the tiles in their initial positions, with the rows listed from top to bottom, and the tiles listed from left to right within a row, where the tiles are represented by numbers 1 to 8, plus ‘x’. For example, this puzzle

1 2 3
x 4 6
7 5 8

is described by this list:

1 2 3 x 4 6 7 5 8

Output

You will print to standard output either the word ``unsolvable’’, if the puzzle has no solution, or a string consisting entirely of the letters ‘r’, ‘l’, ‘u’ and ‘d’ that describes a series of moves that produce a solution. The string should include no spaces and start at the beginning of the line. Do not print a blank line between cases.

Sample Input

2 3 4 1 5 x 7 6 8

Sample Output

ullddrurdllurdruldr

思路

八数码问题是搜索进阶必刷题,也是非常经典的好题,难点主要有两点:

  1. 判重问题(MLE),这个判重还是挺啰嗦的一个东西。由于展开是9的阶乘大概37W种情况,每一种情况判重用如果用set判int,sizeof(int) * 370000要这么多字节。很显然内存是不够的,这道题判重要么康托展开判重要么set对string判重。因为每一种排列情况都是9!以内,所以可以对排列得到的数字组合大小在9个数字中排列属于第几大。

  2. 超时问题,TLE是因为搜索情况太多了,每次都需要重复搜索,最坏的情况下每一次都是搜完37w种情况。数据大肯定过不去,由于这道题最终答案都是12345678x,所以从最终答案出发离线打表并且反向记录答案(从目标结果出发)所有的情况并且记录下来,之后输入就是直接找到对应的数值输出即可。

本题解法不唯一bfs做法是离线。

//逆向bfs打表,目标终点状态一样从目标开始打表出不到40W种的所有情况 
//从起点状态str1回溯目标状态。 
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
int sum[10] = {1,1,2,6,24,120,720,5040,40320,362880};
int dx[] = {0,1,-1,0};
int dy[] = {-1,0,0,1}; 
char dir[5] = {"dlru"};
struct info{
    char way;               //记录方法 
    int pre;                //记录前驱 
}w[370000];
struct Node{
    int ct;                     //康托值 
    int state[9];               //状态 
    int num;                    //9的位置 
};
queue<Node>q;
int Cantor(int s[],int n)                //康托展开判重 
{
    int result = 0;
    for(int i = 0;i < n;i++){
        int cnt = 0;
        for(int j = i+1;j < n;j++){
            if(s[i] > s[j]){            
                cnt++;
            }
        }
        result += cnt * sum[n-i-1];
    }
    return result + 1;			//一定要+1,有些不+1可能会对,WA两页才找到的错误。
}
void bfs()
{
    while(!q.empty()){
        Node ptr = q.front(),p;
        q.pop();
        for(int i = 0;i < 4;i++){
            int nx = ptr.num % 3 + dx[i];                     //转化为二维图数字9的交换后横坐标 
            int ny = ptr.num / 3 + dy[i];                     //转化为二维图数字9的交换后纵坐标 
            int nz = nx + 3*ny;                               //算出新的一维为坐标 
            if(nx < 0 || nx >= 3 || ny < 0 || ny >= 3){       //边界检查 
                continue;
            }
            memcpy(&p,&ptr,sizeof(struct Node));              //结构体赋值 
            p.num = nz;                                       //9的新位置 
            swap(p.state[nz],p.state[ptr.num]);               //新老位置交换 
            p.ct = Cantor(p.state,9);                         //计算康托值 
            if(w[p.ct].pre == -1){                            //判断该康托值是否之前就拓展过,先拓展的一定是最小的 
                w[p.ct].pre = ptr.ct;                         //连接当前状态的上一个状态 
                w[p.ct].way = dir[i];				//这里记录需要搞反向
                q.push(p);
            } 
        }
    }
}
void slove()
{
    while(!q.empty()){
        q.pop();
    }
    int a[9] = {1,2,3,4,5,6,7,8,9};
    Node p;
    for(int i = 0;i < 370000;i++){
        w[i].pre = -1;
    }
    memcpy(p.state,a,sizeof(p.state));        //状态图复制 
    p.ct = 0;                                //初始康托值为0 
    w[p.ct].pre = 0;                        //前驱为0,此时这是树根位置 
    p.num = 8;                                //x的初始位置 
    q.push(p);
    bfs();                                    //预处理所有的状态 
}
int main()
{
    slove();                //预处理 
    char s[100];
    while(gets(s)){
        int len = strlen(s);
        int t[9],j = 0;
        for(int i = 0;i < len;i++){
            if(s[i] >= '1' && s[i] <= '8'){
                t[j++] = s[i] - '0';
            }
            else if(s[i] == 'x'){
                t[j++] = 9;
            }
        }
        int ans = Cantor(t,9);
        if(w[ans].pre == -1){
            printf("unsolvable\n");
        }
        else{
            while(ans){
                printf("%c",w[ans].way);    //打印方法 
                ans = w[ans].pre;            //向前回溯 
            }
            printf("\n");
        }
    }
    return 0;
}
//以目标节点为中心,拓展出一棵树,每个节点都是一种状态。

A* 或者 IDA* + 康托展开 + 无解情况剪枝也能做,只不过这两个解法是在线强行算。具体这个无解情况剪枝认真研究也要研究一天,感觉上面离线打表的做法比较好。

八数码问题A*搜索的h函数
第一种,每个数字到对应位置需要移动的格子数目
第二种,有多少个在对应位置的个数

其实Astar搜索无非就是bfs搜索的拓展,只不过加入了估价函数,对于这个估价函数呢你也可以加入自己的思路去把它设计的更完美,让搜索跑的更快。当然Astar搜索对于普通bfs搜索的优点就是在有解的情况下更快,如果题目存在无解的情况那基本上没啥大的提升,反正这两种搜索都会搜索完所有的可能情况,只不过先后顺序可能不一样罢了,如果题目没有无解的情况或者你能够把无解的情况单独拿出来剪枝那么使用Astar能够加快搜索,如果无解的情况你剪枝不掉或者剪枝掉意义不大那么普通广搜和Astar理论上差不多。

下面代码采用的是第一种估价计算函数。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
const int maxn = 370000;
int sum[10] = {1,1,2,6,24,120,720,5040,40320,362880};
bool visited[maxn];
int ed[] = {1,2,3,4,5,6,7,8,9};
int dx[] = {0,-1,1,0};
int dy[] = {1,0,0,-1};
char dir[5] = {"dlru"};
struct info{
    int state[10];
    int g,f,h,num;
    char way[50];
    bool operator <(const info s)const{
        if(s.f == f){
            return s.g < g;
        }
        else{
            return s.f < f;
        }
    }
};
int Cantor(int s[])        //康托展开 
{
    int result = 0;
    for(int i = 0;i < 9;i++){
        int cnt = 0;
        for(int j = i+1;j < 9;j++){
            if(s[i] > s[j]){
                cnt++;
            }
        }
        result += cnt*sum[8-i];
    }
    return result + 1;
}
int Manhaton(int st[])        //曼哈顿估计,每一个数字归位需要多少步
{
    int result = 0;
    for(int i = 0;i < 9;i++){
        int num = st[i] - 1;
        int x = i/3;
        int y = i%3;
        int a = num/3;
        int b = num%3;
        result += abs(x-a) + abs(y-b); 
    } 
    return result;
}
priority_queue<info>q;
int Astar()
{
    while(!q.empty()){
        info ptr = q.top(),p;
        q.pop();
        for(int i = 0;i < 4;i++){
            int nx = ptr.num%3 + dx[i];
            int ny = ptr.num/3 + dy[i];
            int nz = nx + 3*ny;
            if(nx < 0 || nx >= 3 || ny < 0 || ny >= 3){
                continue;
            }
            memcpy(&p,&ptr,sizeof(struct info));
            swap(p.state[ptr.num],p.state[nz]);
            p.num = nz;
            int ct = Cantor(p.state);        
            if(visited[ct]){            //判重
                continue;
            }
            p.way[ptr.g] = dir[i]; 
            p.g = ptr.g + 1;
            p.h = Manhaton(p.state);
            p.f = p.g + p.h;
            if(p.h == 0){                //目标状态 
                p.way[p.g] = '\0';
                printf("%s\n",p.way);
                return p.g;
            }        
            q.push(p);
            visited[ct] = true;
        }
    }
    return -1;
}
int main()
{
    char s[100];
    while(gets(s)){
        int len = strlen(s);
        int t[10],j = 0,p_x = 0;
        for(int i = 0;i < len;i++){
            if(s[i] == 'x'){
                p_x = j;
                t[j++] = 9;
                continue;
            }
            else if(s[i] >= '1' && s[i] <= '8'){
                t[j++] = s[i] - '0';
            }
        }
        int sum = 0;
        for(int i = 0;i < j;i++){
            if(t[i] == 9){
                continue;
            }
            for(int k = 0;k < i;k++){
                if(t[k] == 9){
                    continue;
                }
                if(t[i] < t[k]){
                    sum++;
                } 
            }
        }
        memset(visited,false,sizeof(visited));
        while(!q.empty()){
            q.pop();
        }
        info p;
        memcpy(p.state,t,sizeof(t));
        p.g = 0;p.h = Manhaton(t);
        if(p.h == 0){
            printf("\n");
            continue;
        }
        if(sum % 2 == 1){			//剪枝掉无解的情况,不然还是会超时。
            printf("unsolvable\n");
            continue;
        }
        p.f = p.h + p.g;p.num = p_x;
        p_x = Cantor(p.state);
        visited[p_x] = true;
        q.push(p);
        Astar();
    }
    return 0;
} 

愿你走出半生,归来仍是少年~
 
  
   
    
     
      
       
        
         
          
           
            
             
              
               

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值