BFS 状态转移模型

题目描述

#10027. 「一本通 1.4 例 2」魔板 - 题目 - LibreOJ (loj.ac) 原题来自:USACO 3.2.5

Rubik 先生在发明了风靡全球魔方之后,又发明了它的二维版本——魔板。这是一张有  个大小相同的格子的魔板:

1 2 3 4
8 7 6 5

我们知道魔板的每一个方格都有一种颜色。这  种颜色用前  个正整数来表示。可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。对于上图的魔板状态,我们用序列  来表示。这是基本状态。

这里提供三种基本操作,分别用大写字母 ABC 来表示(可以通过这些操作改变魔板的状态):

  • A:交换上下两行;
  • B:将最右边的一列插入最左边;
  • C:魔板中央作顺时针旋转。

下面是对基本状态进行操作的示范:

A

8 7 6 5
1 2 3 4

B

4 1 2 3
5 8 7 6

C

1 7 2 4
8 6 3 5

对于每种可能的状态,这三种基本操作都可以使用。

你要编程计算用最少的基本操作完成基本状态到特殊状态的转换,输出基本操作序列。

输入格式

输入仅一行,包括  个整数,用空格分开,表示目标状态。

输出格式

输出文件的第一行包括一个整数,表示最短操作序列的长度。 第二行为在字典序中最早出现的操作序列。

样例

输入

2 6 8 4 5 7 3 1

输出

7
BCABCCB

数据范围与提示

输入数据中的所有数字均为  到  之间的整数。

思路

把当前魔板状态看做一个节点, 可以进行一系列操作来转换到另一个节点, 显然需要解决四个问题:

  • 如何建立数据结构来表示当前节点的魔板状态 结构体可以解决
  • 如何判断当前魔板状态是最终要求的状态 挨个判断即可
  • 如何从一个状态转移到另一个状态 有状态就很好办
  • 如何在转移状态时记录当前路径 类似于BFS求方案, 从终末状态逆向泛洪, 记录prev数组指向上一个和当前走的操作。

存状态通常使用哈希法, 把整个状态用一个最小整数存下来。 可以自己手写哈希, 或者用康拓展开, 或者用STL的map/unordered_map

代码

char g[2][4];
unordered_map<string, int> dist;
unordered_map<string, pair<int, string>> pre;
queue<string> q;
string start, endstate;
 
void set(string s)
{
    for (int i = 0; i < 4; i++)
        g[0][i] = s[i];
    for (int i = 3, j = 4; i >= 0; i--, j++)
        g[1][i] = s[j];
}
 
string get()
{
    string s;
    for (int i = 0; i < 4; i++)
        s += g[0][i];
    for (int i = 3; i >= 0; i--)
        s += g[1][i];
    return s;
}
 
string move0(string t)
{
    set(t);
    for (int i = 0; i < 4; i++)
        swap(g[0][i], g[1][i]);
    return get();
}
string move1(string t)
{
    set(t);
    char v0 = g[0][3], v1 = g[1][3];
    for (int i = 3; i >= 0; i--)
        g[0][i] = g[0][i - 1], g[1][i] = g[1][i - 1];
    g[0][0] = v0, g[1][0] = v1;
    return get();
}
string move2(string t)
{
    set(t);
    char v0 = g[0][1];
    g[0][1] = g[1][1];
    g[1][1] = g[1][2];
    g[1][2] = g[0][2];
    g[0][2] = v0;
    return get();
}
 
void bfs(string start, string end)
{
    q.push(end);
    dist[end] = 0;
    while (!q.empty())
    {
        auto t = q.front();
        q.pop();
 
        string m[3];
        m[0] = move0(t);
        m[1] = move1(t);
        m[2] = move2(t);
        for (int i = 0; i < 3; i++)
        {
            string str = m[i];
            if (dist.count(str))
                continue;
            dist[str] = dist[t] + 1;
            pre[str] = {char(i + 'A'), t};
            if (str == start)
                return;
            q.push(str);
        }
    }
}
 
int main()
{
    for (int i = 0; i < 8; i++)
    {
        int t;
        cin >> t;
        start += char(t + '0');
        endstate += char(i + '1');
    }
 
    bfs(start, endstate);
 
    cout << dist[start] << endl;
 
    string res;
    while (start != endstate)
    {
        res += pre[start].first;
        start = pre[start].second;
    }
    rev(res);
    if (res.size())
        cout << res << endl;
    return 0;
}