Eight II HDU - 3567

题目链接:Eight II HDU - 3567

===================================================

Eight II

Time Limit: 2000 ms
Memory Limit: 65536 kB

Description

Eight-puzzle, which is also called “Nine grids”, comes from an old game.

In this game, you are given a 3 by 3 board and 8 tiles. The tiles are numbered from 1 to 8 and each covers a grid. As you see, there is a blank grid which can be represented as an ‘X’. Tiles in grids having a common edge with the blank grid can be moved into that blank grid. This operation leads to an exchange of ‘X’ with one tile.

We use the symbol ‘r’ to represent exchanging ‘X’ with the tile on its right side, and ‘l’ for the left side, ‘u’ for the one above it, ‘d’ for the one below it.
在这里插入图片描述
A state of the board can be represented by a string S using the rule showed below.
在这里插入图片描述
The problem is to operate an operation list of ‘r’, ‘u’, ‘l’, ‘d’ to turn the state of the board from state A to state B. You are required to find the result which meets the following constrains:

  1. It is of minimum length among all possible solutions.
  2. It is the lexicographically smallest one of all solutions of minimum length.

Input

The first line is T (T <= 200), which means the number of test cases of this problem.

The input of each test case consists of two lines with state A occupying the first line and state B on the second line.
It is guaranteed that there is an available solution from state A to B.

Output

For each test case two lines are expected.

The first line is in the format of “Case x: d”, in which x is the case number counted from one, d is the minimum length of operation list you need to turn A to B.
S is the operation list meeting the constraints and it should be showed on the second line.

Sample Input

2
12X453786
12345678X
564178X23
7568X4123

Sample Output

Case 1: 2
dd
Case 2: 8
urrulldr

===================================================

这里是要求是给出 起点和终点序列 然后得出最短操作 而且相同长度要求最小字典顺序(重点,我这里wrong了很多次 看题目才知道)

第一遍:就是A* + map 尝试 然后 timelimit。

第二遍:看其他人代码,有两种,一种就是设立limit的dfs;这几天了解一下 这是IDA算法。迭代加深的A算法。

f(x) = 层数 + 与目标序列的相同字符曼哈顿距离之和;然后从第一个输入的序列的f(x)为limit,然后limit逐步以最小量增加为dextd直到找到答案位置。

===================================================

知识补充:IDA*算法——这里我直接给出大牛代码的链接。https://www.cnblogs.com/DOLFAMINGO/p/7538577.html

在这里插入图片描述
在这里插入图片描述

===================================================

#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>

using namespace std;
//int的取值范围为: -2^31——2^31-1,即-2147483648——2147483647
const int INF = 2e9;

int Y[10],M[10],op=1,nextd;
char C[1234];
int changeId[9][4] = {{3,-1,1,-1},{4,0,2,-1},{5,1,-1,-1},
                        {6,-1,4,0},{7,3,5,1},{8,4,-1,2},
                        {-1,-1,7,3},{-1,6,8,4},{-1,7,-1,5}};
char d[5] = "dlru";

int forLimit(int *a){
    int ans = 0;
    for(int i=0;i<9;i++) if(a[i]!=0) ans += abs(i/3 - M[a[i]]/3) + abs(i%3 - M[a[i]]%3);
    return ans;
}

bool IDAstar(int k,int step,int pre,int limit){
    int h = forLimit(Y);
    if(h==0){
        printf("Case %d: %d\n",op++,step);
        C[step] = '\0';puts(C);
        return true;
    }
    if(h+step>limit){
        nextd = min(nextd,h+step);
        return false;
    }
    for(int i=0;i<4;i++){
        int pos = changeId[k][i];
        if(pos==-1||i+pre==3) continue;
        C[step] = d[i];
        swap(Y[k],Y[pos]);
        if(IDAstar(pos,step+1,i,limit)) return true;
        swap(Y[k],Y[pos]);
    }
    return false;
}



int main()
{
    int _;scanf("%d",&_);
    while(_--){
        char a[10],b[10];
        int k;
        scanf("%s%s",a,b);
        for(int i=0;i<9;i++)
            if(a[i]=='X') Y[i] = 0,k = i;
            else Y[i] = a[i] - '0';

        for(int i=0;i<9;i++)
            if(b[i]=='X') M[0] = i;
            else M[b[i]-'0'] = i;

        //for(int i=0;i<9;i++) cout<<Y[i];cout<<endl;
        //for(int i=0;i<9;i++) cout<<M[i];cout<<endl;
        //---------------------------
        for(int limit = forLimit(Y);;limit = nextd){
            nextd=INF;
            if(IDAstar(k,0,INF,limit)) break;
        }
    }
    return 0;
}

自己码了一遍,发现易错点在于

  1. nextd = INF 需要每次循环 重新定义为INF
  2. f(x) = h(x) + g(x).
    这里构造启发函数,先试着用简单的不同字符数来运算,结果跑不出来,后改为曼哈顿距离,然后试着连着X字符也计算,发现wrong,这里这样的确不是最优,但是举不出反例,所以也不知道为什么?最后就用作者一样,不算X的曼哈顿距离,然后就通过了。
  3. 最后最重要还是减枝,避免死循环。

===================================================

另一种就是预处理 ,这里很巧妙 我想了一早上 想通了;重点 x 是操作单位不能动,其他字符是可替换的。

举例子:645X312 ---->456X123 的操作 和 123X456---->231X564的操作一样 为什么?因为这只是简单字符替换 ,这种替换并不影响结果。甚至645X312可以替换成qweXyui,不影响的。

然后就是12345678X,1234567X8…9种X在不同位置的预处理,然后通过给出起点X位置进行得出结果。

重点 要 {dlru}这样进行遍历 因为需要最小字典序列

===================================================

#include <iostream>
#include <queue>
#include <cstring>
#include <string>

using namespace std;

const int M = 400000;

int vis[9][M];
int pre[9][M];
//-----------------------------------------------------------
void show(int *a){
    for(int i=0;i<9;i++) cout<<a[i];cout<<endl;
}
//------------------------------------------------------------
int changeId[9][4] = {{3,-1,1,-1},{4,0,2,-1},{5,1,-1,-1},
                        {6,-1,4,0},{7,3,5,1},{8,4,-1,2},
                        {-1,-1,7,3},{-1,6,8,4},{-1,7,-1,5}};
char d[5] = {'d','l','r','u'};

//康托展开======================================================
const int fk[10] = {1,1,2,6,24,120,720,5040,40320,362880};
int cantor(int *a){
    int ans = 0;
    for(int i=0;i<8;i++){
        int small = 0;
        for(int j=i+1;j<9;j++) if(a[j]<a[i]) small++;
        ans += small*fk[8-i];
    }
    return ans;
}
//----------------------------------------------------------
struct node{
    int num[9],zero,hashcode;
};

void bfs(int x){

    node now;
    for(int i=0,j=1;i<9;i++)
        if(i==x) now.num[i] = 0;
        else now.num[i] = j++;
    now.zero=x;
    now.hashcode = cantor(now.num);

    queue<node> q;
    q.push(now);
    vis[x][now.hashcode] = 1;

    while(!q.empty()){
        node p = q.front();q.pop();
        int zero = p.zero,hashcode = p.hashcode;

        for(int i=0;i<4;i++){
            if(changeId[zero][i] == -1) continue;
            node temp = p;
            temp.zero = changeId[zero][i];
            swap(temp.num[zero],temp.num[temp.zero]);
            temp.hashcode = cantor(temp.num);
            if(vis[x][temp.hashcode]==-1){
                vis[x][temp.hashcode] = i;
                pre[x][temp.hashcode] = hashcode;
                q.push(temp);
            }
        }
    }
}
//预处理=================================================
void init(){
    memset(vis,-1,sizeof vis);
    memset(pre,-1,sizeof pre);
    for(int i=0;i<9;i++) bfs(i);
}
//---------------------------------------------------------
int main(){
    init();
    int _,op=1;
    scanf("%d",&_);
    while(_--){
        //----------------------
        int k;
        char a[10],b[10];
        int c[10],u[10];
        scanf("%s%s",a,b);
        //printf("%s\n%s\n",a,b);

        //进行符号兑换:c数组为兑换数组,得到兑换后的目标数组和X位置========
        for(int i=0,j=1;i<9;i++)
            if(a[i]=='X') k=i;
            else c[a[i]-'0'] = j++;
        for(int i=0;i<9;i++)
            if(b[i]=='X') u[i] = 0;
            else u[i] = c[b[i]-'0'];

            //show(u);
        //===============================
        int can = cantor(u);
        string ans = "";
        while(can != -1){
            ans = d[vis[k][can]] +  ans;
            can = pre[k][can];
        }

        printf("Case %d: %d\n",op++,ans.length()-1);
        cout<<ans.substr(1)<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盐太郎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值