uva1604 Cubic Eight-Puzzle 低级解法:暴力搜索+剪枝+字符串双hash压缩

参考博客

最优剪枝部分

hash部分

题意(原题链接

给一个八个立方体,每个立方体的相对的面有相同的颜色,其他4个面的颜色与这两个对立面不一样,一共三种颜色。八个立方体加上一个空位,在平面上排成一个九宫格,且具有序号。初始的排列状态相同,但是空格的位置不一定,由题目给出。如下图所示。给定初始状态的空格的位置,和目标状态的朝上的面的颜色,求最少“翻转”——将与空格位置相邻的立方体沿立方体与空格的公共棱翻转到空格,几步可以到达目标状态。若步数超过30步,输出-1 。

思路

  • bfs
  • 判重:字符串双hash压缩
  • 根据题意建立xyz坐标,z表示朝上的面的颜色,xy方向与图相同,代表其穿过的立方体的面的颜色。则向哪个坐标(x或y)翻转,就交换哪个坐标穿过的颜色和朝上的面的颜色。
  • 最优剪枝:当前步数steps加上“当前状态与目标状态的朝上的面不同颜色的立方体的数量”dif,若这个值大于31,则无法到达。因为一次翻转只能改变两个位置(空格和被翻转的立方体),n次翻转最多能改变n-1个位置,最理想的情况是改变的n-1个位置刚好使得当前状态到达目标状态。故有steps+dif-1<=30,即steps+dif<=31。

AC代码,时间2320ms

注:个人阅读习惯从main开始,按顺序读,所以注释风格可能也按这个顺序。

ps:uDebug上有个测试数据没过……

#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>

using namespace std;

struct cube {
    char v;
    char x;// 知道两个方向的颜色,第三个方向的颜色就知道了,故忽略y
};

struct State {
    vector<cube> cubes;
    int d;// 步数
};

int x, y;// 初始空格位置
int des_x, des_y;// 目标空格位置

int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};

char ans[9];

bool vst[4999][5987];

// 双hash
void Hash(const State& st,int(&hash) [2]){
    hash[0]=hash[1]=0;
    const int Base1=11,Mod1=4999;
    const int Base2=13,Mod2=5987;
    for(int i=0;i<9;i++){
        hash[0]=(hash[0]*Base1+(st.cubes[i].v-'0'))%Mod1;
        if(st.cubes[i].v=='E') continue;
        hash[1]=(hash[1]*Base2+(st.cubes[i].x-'0'))%Mod2;
    }
}

bool flip(State &v, const int &xx, const int &yy, const int &dir) {
    int xxx = xx + dx[dir], yyy = yy + dy[dir];// 移动后坐标
    // 越界
    if (xxx > 2 || xxx < 0 || yyy > 2 || yyy < 0) return false;
    int idx1 = xx * 3 + yy, idx2 = xxx * 3 + yyy;// 坐标对应的数组下标,分别为空格和翻转的立方体
    v.cubes[9].x = '0' + xxx, v.cubes[9].v = '0' + yyy;// 保存空格位置
    // 朝哪个方向翻转,就会交换z的颜色和那个方向的颜色
    // 只将空格位置改成立方体的颜色即可,新空格位置只要v改成'E'即可
    if (dx[dir]) {
        v.cubes[idx1].v = v.cubes[idx2].x;
        v.cubes[idx1].x = v.cubes[idx2].v;
    } else {
        char str[] = {'B', 'W', 'R'};// 根据两个x和z确定y的颜色
        char y2;
        for (int i = 0; i < 3; i++) {
            if (v.cubes[idx2].v != str[i] && v.cubes[idx2].x != str[i]) y2 = str[i];
        }
        v.cubes[idx1].v = y2;
        v.cubes[idx1].x = v.cubes[idx2].x;
    }
    v.cubes[idx2].v = 'E';
    return true;
}

// 计算搜索的盘面和目标盘面的差异数
int diff(const State &cubes) {
    int d = 0;
    for (int i = 0; i < 9; i++) {
        if (cubes.cubes[i].v != ans[i]) ++d;
    }
    return d;
}

int bfs() {
    memset(vst, 0, sizeof(vst));
    queue<State> q;
    State begin;
    begin.d = 0, begin.cubes = vector<cube>(9, {'W', 'B'});
    begin.cubes[x * 3 + y].v = 'E';// 起点
    cube loc;
    loc.x = '0' + x, loc.v = '0' + y;// 数组尾记录空格位置
    begin.cubes.push_back(loc);
    q.push(begin);
    int hashB[2];
    Hash(begin,hashB);
    vst[hashB[0]][hashB[1]] = true;
    while (!q.empty()) {
        State u = q.front();q.pop();
        int xx = u.cubes[9].x - '0', yy = u.cubes[9].v - '0';// 空格位置
        for (int i = 0; i < 4; i++) {
            State cp = u;
            if (flip(cp, xx, yy, i)) {// 移动cube
                // 判重
                int hash[2];
                Hash(cp,hash);
                if (vst[hash[0]][hash[1]]) continue;
                vst[hash[0]][hash[1]] = true;
                int dif = diff(cp);// 与目标有几个方块的差异
                cp.d = u.d + 1;
                if (dif == 0) return cp.d;
                    // 剪枝:n个差异最少需要n-1次移动,steps+dif-1小于等于30
                else if (dif + cp.d > 31) continue;
                q.push(cp);
            }
        }
    }
    return -1;
}

#define TEST

int main() {
#ifdef TEST
    freopen("D:\\college\\clionworkplace\\test_c++2\\in.txt", "r", stdin);
    freopen("D:\\college\\clionworkplace\\test_c++2\\out.txt", "w", stdout);
#endif
    while (scanf("%d%d", &x, &y) == 2 && x) {
        getchar();// 清除换行符
        char buf[3][3];
        // 按题目所给的坐标顺序读入
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                scanf("%c", &buf[j][i]);
                if (buf[j][i] == 'E') {
                    des_x = j, des_y = i;// 初始空格位置
                }
                getchar();// 清除字母间空格
            }
        }
        // 转成一维保存
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                ans[i * 3 + j] = buf[i][j];
            }
        }
        // 先判断是否已经满足目标状态
        if (x == des_x + 1 && y == des_y + 1) {
            bool all_w = true;
            for (int i = 0; i < 9; i++) {
                if (ans[i] != 'W' && ans[i] != 'E') {
                    all_w = false;
                    break;
                }
            }
            if (all_w) {
                printf("0\n");
                continue;
            }
        }
        x -= 1, y -= 1;// 转为(0,0)为起点
        printf("%d\n", bfs());
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值