https://www.acwing.com/problem/content/1109/
Rubik先生在发明了风靡全球的魔方之后,又发明了它的二维版本——魔板。这是一张有 8 8 8个大小相同的格子的魔板:
1 2 3 4
8 7 6 5
我们知道魔板的每一个方格都有一种颜色。这 8 8 8种颜色用前 8 8 8个正整数来表示。可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。对于上图的魔板状态,我们用序列 ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ) (1,2,3,4,5,6,7,8) (1,2,3,4,5,6,7,8)来表示,这是基本状态。这里提供三种基本操作,分别用大写字母 A A A, B B B, C C C来表示(可以通过这些操作改变魔板的状态):
A A A:交换上下两行;
B B B:将最右边的一列插入到最左边;
C C C:魔板中央对的4个数作顺时针旋转。
下面是对基本状态进行操作的示范:
A A A:
8 7 6 5
1 2 3 4
B B B:
4 1 2 3
5 8 7 6
C C C:
1 7 2 4
8 6 3 5
对于每种可能的状态,这三种基本操作都可以使用。你要编程计算用最少的基本操作完成基本状态到特殊状态的转换,输出基本操作序列。注意:数据保证一定有解。
输入格式:
输入仅一行,包括 8 8 8个整数,用空格分开,表示目标状态。
输出格式:
输出文件的第一行包括一个整数,表示最短操作序列的长度。如果操作序列的长度大于 0 0 0,则在第二行输出字典序最小的操作序列。
数据范围:
输入数据中的所有数字均为 1 1 1到 8 8 8之间的整数。
思路是BFS,其实是个隐式图搜索的问题,同时需要存路径。可以正着搜,同时用一个数组 p p p存每个状态是由哪个状态转移过来的(同时还要记录具体是用了哪个操作转移过来的),最后返回路径的时候从终点向前找,然后逆序一下。至于要输出字典序最小的操作序列,只需要在BFS的时候按字典序从小到大枚举操作即可(可以简单证明一下。首先BFS确实能搜到最短路,其次,如果存在字典序更小的路径,那么考虑该路径与算法所得路径第一次不同的操作,这个操作的字典序一定比算法中的相同位置的操作更小,但是每个状态在入队的时候,是先进先出的,所以该操作所扩展出来的状态也会先入队,那么该操作所得路径第一次走到终点的时候必然会被上述算法记录下来,也就是说算法所得路径就是那个字典序更小的路径,这就矛盾了)。代码如下:
#include
#include
#include
#include
#include
using namespace std;
string st, ed;
unordered_map<string, int> dist;
unordered_map<string, pair<char, string> > pre;
queue<string> q;
// 返回对cur分别进行操作A、B、C所得的字符串
vector<string> move(string cur) {
vector<string> res;
string m1 = cur.substr(4, 4);
m1 += cur.substr(0, 4);
string m2;
m2 += cur[3] + cur.substr(0, 3);
m2 += cur[7] + cur.substr(4, 3);
string m3 = cur;
m3[1] = cur[5], m3[2] = cur[1], m3[5] = cur[6], m3[6] = cur[2];
res.push_back(m1);
res.push_back(m2);
res.push_back(m3);
return res;
}
void bfs() {
if (st == ed) return;
q.push(st);
dist[st] = 0;
while (!q.empty()) {
string t = q.front();
q.pop();
vector<string> nexts = move(t);
for (int i = 0; i < 3; i++) {
string s = nexts[i];
if (dist.count(s)) continue;
dist[s] = dist[t] + 1;
pre[s] = {char(i + 'A'), t};
if (s == ed) return;
q.push(s);
}
}
}
int main() {
int x;
for (int i = 0; i < 8; i++) {
cin >> x;
ed += char(x + '0');
}
for (int i = 0; i < 8; i++) st += char(i + '1');
reverse(st.begin() + 4, st.end());
reverse(ed.begin() + 4, ed.end());
bfs();
cout << dist[ed] << endl;
string res;
while (ed != st) {
res += pre[ed].first;
ed = pre[ed].second;
}
// 操作路径要逆序一下
reverse(res.begin(), res.end());
if (!res.empty()) cout << res << endl;
return 0;
}
时空复杂度 O ( V ) O(V) O(V), V V V是搜索的状态总个数。