【双向BFS+康托展开】Eight Ⅱ HDU - 3567

HDU - 3567 Eight II

题意:八数码问题,给定初始状态与目标状态,输出字典序最小的移动路径和步数。

思路:

  • 双向BFS:

    以初始状态和目标状态为两个起点,同时出发,汇合时即答案

    但需要注意,从初始状态出发的正向BFS所得路径必是字典序最小的(前提是方向遍历是按字典序从小到大);从目标状态出发的反向BFS则不一定,当反向BFS遇到之前搜索过的反向状态时,需要比较新的路径字典序和已有的路径字典序的大小。

    以及需要把棋盘信息存进结构体里面,以省去逆康托展开的步骤,否则会TLE

#include<iostream>
#include<stdlib.h>
#include<time.h>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<cstring>
#include<cstdio>
#include <string>
/*#define mem(a,b) memset(a,b,sizeof(a))
#define f(i,a,b) for(int i=a;i<=b;i++)
#define af(i,a,b) for(int i=a;i>=b;i--)
*/

using namespace std;

typedef long long LL;
const LL INF = 0x3f3f3f3f;
const int maxn = 362885;

const int FAC[] = { 1,1,2,6,24,120,720,5040,40320,362880,3628800 };

int cantor(int* a) {//算出全排列对应的哈希值
    int x = 0;
    for (int i = 0; i < 9; i++) {
        int smaller = 0;
        for (int j = i + 1; j < 9; j++) {
            if (a[j] < a[i]) smaller++;
        }
        x += FAC[9 - i - 1] * smaller;
    }
    return x+1;
    //注意全排列数组a是从零开始的
}

/*
void decantor(int x,int*ans) {//x哈希值,n数字个数,a算出的全排列
    x--;
    vector<int> v;
    for (int i = 1; i <= 9; i++) v.push_back(i);
    for (int i = 0; i < 9; i++) {
        int r;
        r = x / FAC[9 - i - 1];
        x = x % FAC[9 - i - 1];
        sort(v.begin(), v.end());
        ans[i] = v[r];
        v.erase(v.begin() + r);
    }
    //注意算出的全排列数组ans是从0开始的
}
*/

//描述每个状态需要有以下信息
struct node {
    int a[15];
    int hash;
    int pos;
    int deep;
};

string path[maxn];
int vis_go[maxn];
int vis_back[maxn];
int dx[] = { 1,0,0,-1 };
int dy[] = { 0,-1,1,0 };
char go_op[] = "dlru";
//正向的记录
//要求字典序最小,所以在尝试的时候也从小到大尝试
char back_op[] = "urld";
//反向的记录

queue<node> q_go;//正向的队列
queue<node> q_back;//反向的队列

void bfs(node begin,node end) {
    if (begin.hash == end.hash) {
        cout << 0 << endl << endl;
        return;
    }
    q_go.push(begin);
    vis_go[begin.hash] = 1;
    path[begin.hash] = "";
    q_back.push(end);
    vis_back[end.hash] = 1;
    path[end.hash] = "";

    int L = 0;
    while (!q_go.empty() || !q_back.empty()) {
        while (!q_go.empty() && q_go.front().deep == L) {
            node tmp = q_go.front();
            q_go.pop();
            for (int i = 0; i < 4; i++) {
                int nx = tmp.pos / 3 + dx[i];
                int ny = tmp.pos % 3 + dy[i];
                int npos = nx * 3 + ny;
                if (nx < 0 || ny < 0 || nx>2 || ny>2) continue;
                node now=tmp;
                swap(now.a[tmp.pos], now.a[npos]);
                now.hash = cantor(now.a);
                now.pos = npos;
                now.deep = tmp.deep + 1;
                if (vis_back[now.hash]) {
                    //正向,进行本次操作之后变成了反向搜索过的状态
                    //此时正反汇合了,路径已贯通
                    cout << (int)path[now.hash].size() + (int)path[tmp.hash].size() + 1 << endl;
                    //+1是本次操作的一次
                    reverse(path[now.hash].begin(), path[now.hash].end());
                    //此时path[now.hash]储存的是反向搜索的路径,但由于记录是正向的,这里要反转字符串
                    cout << path[tmp.hash] << go_op[i] << path[now.hash] << endl;
                    //先输出正向搜索的路径,以及本次的操作,然后再输出反向搜索的路径
                    return;
                }
                if (vis_go[now.hash]) continue;
                //正向,进行本次操作后变成了正向搜索过的状态,就跳过
                q_go.push(now);
                vis_go[now.hash] = 1;
                path[now.hash] = path[tmp.hash] + go_op[i];

                //cout << "go:"<<path[now.hash] << endl;
            }
        }

        while (!q_back.empty() && q_back.front().deep == L) {
            node tmp = q_back.front();
            q_back.pop();
            for (int i = 0; i < 4; i++) {
                int nx = tmp.pos / 3 + dx[i];
                int ny = tmp.pos % 3 + dy[i];
                int npos = nx * 3 + ny;
                if (nx < 0 || ny < 0 || nx>2 || ny>2) continue;
                node now=tmp;
                swap(now.a[tmp.pos], now.a[npos]);
                now.hash = cantor(now.a);
                now.pos = npos;
                now.deep = tmp.deep + 1;

                if (vis_go[now.hash]) continue;
                //反向,进行本次操作后遇到了正向搜索过的状态,跳过
                if (vis_back[now.hash] && path[now.hash].size() && path[now.hash][(int)path[now.hash].size() - 1] < back_op[i]) continue;
                //如果遇到了反向搜索过的状态
                //比较已有路径的最后一个字母和本次操作的字母大小
                //如果本次操作字母更大,说明替换后新的路径字典序更大,不能替换
                //否则用新的路径替换掉原有路径

                q_back.push(now);
                vis_back[now.hash] = 1;
                path[now.hash] = path[tmp.hash] + back_op[i];

                //cout << "back:" << path[now.hash] << endl;
            
            }
        }

        L++;
        //两个队列进行下一层搜索
    }
}

void clear() {
    while (!q_go.empty()) q_go.pop();
    while (!q_back.empty()) q_back.pop();
    memset(vis_go, 0, sizeof(vis_go));
    memset(vis_back, 0, sizeof(vis_back));
}


int main()
{
    ios::sync_with_stdio(false);
    int t; cin >> t; getchar();// while (t--) {
    for (int k = 1; k <= t; k++) {
        clear();
        string s;
        node temp[3];

        for (int i = 1; i <= 2; i++) {
            //初始状态和目标状态入队,双向BFS
            getline(cin, s);
            int j = 0;
            for (int p = 0; p < s.size(); p++) {
                if (s[p] == 'X') {
                    temp[i].a[j++] = 9;
                    temp[i].pos = j - 1;
                }
                else if (isdigit(s[p])) temp[i].a[j++] = s[p] - '0';
            }
            temp[i].hash = cantor(temp[i].a);
            temp[i].deep = 0;
        }
        
        cout << "Case " << k << ": ";
        bfs(temp[1], temp[2]);

    }
    return 0;
}
}
posted @ 2020-07-24 15:46  StreamAzure  阅读(138)  评论(0编辑  收藏  举报