AcWing845 八数码1.0

题目描述

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入格式

输入初始状态,一行九个数字,空格用0表示

输出格式

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)

输入

283104765

输出

4

思路

容易想到用bfs来搜索,那么怎么记录下每一种状态是否被搜索到了呢?
思考1:每一个状态用一个字符串存储,要9个字节,需要的空间太大了。
思考2:每一个状态用一个int来存储,因为$87654321<2^{31}$,但是还是太大了。
思考3:注意到总共的状态数目为9!个,可以用康托展开来得到每一个状态对应的hash值,这样就能过了。
思考4:其实,stl中的unordered_map(哈希表实现)同样可以完成这个任务,更加的方便,所花费的空间和方案3差不多。

思考4:代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <unordered_map>
#include <queue>
using namespace std;
queue<string> q;
string e = "123804765";
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
unordered_map<string, int> h;

void bfs(string s){
h[s] = 0;
q.push(s);
while(!q.empty()){
string f = q.front();
if(f == e){
cout << h[e];
return;
}
q.pop();
int k = f.find('0');
int x = k /3, y = k % 3;
int dist = h[f];
for(int i = 0; i < 4; i++){
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a <= 2 && b >=0 && b <= 2 ){
swap(f[k], f[a * 3 + b]);
if(!h.count(f)){
q.push(f);
h[f] = dist + 1;
}
swap(f[k], f[a * 3 + b]);
}
}
}
cout << "-1";
}
int main()
{
string s;
char c;
for(int i = 0; i < 9; i++){
cin >> c;
s += c;
}
bfs(s);
}

AcWing179 八数码2.0

题目描述

在一个 3×3的网格中,1∼8 这 8 个数字和一个 X 恰好不重不漏地分布在这 3×3的网格中。

例如:

1
2
3
1 2 3
X 4 6
7 5 8

在游戏过程中,可以把 X 与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1
2
3
1 2 3
4 5 6
7 8 X

例如,示例中图形就可以通过让 X 先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1
2
3
1 2 3   1 2 3   1 2 3   1 2 3
X 4 6 4 X 6 4 5 6 4 5 6
7 5 8 7 5 8 7 X 8 7 8 X

X 与上下左右方向数字交换的行动记录为 udlr

现在,给你一个初始网格,请你通过最少的移动次数,得到正确排列。

输入格式

输入占一行,将 3×3 的初始网格描绘出来。

例如,如果初始网格如下所示:

1
2
3
1 2 3 
x 4 6
7 5 8

则输入为:1 2 3 x 4 6 7 5 8

输出格式

输出占一行,包含一个字符串,表示得到正确排列的完整行动记录。

如果答案不唯一,输出任意一种合法方案即可。

如果不存在解决方案,则输出 unsolvable

输入

1
2  3  4  1  5  x  7  6  8 

输出

1
ullddrurdllurdruldr

思路

基本想法同上,但还可以优化

思考5:A*算法

A*

使用A*算法需注意一下几点:

  • 估计距离一定要小于等于实际距离。这道题中估计距离为,每个数字到它应该到的位置的曼哈顿距离之和。
  • 除终点外,所有点在出队时,不能保证找到最短路径
  • 需要保证有解,否则算法复杂度会退化为朴素解法。对于这道题,可以证明有解的充要条件为:逆序对数量为偶数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <math.h>
#include <queue>
#include <deque>
#include <unordered_map>
using namespace std;
typedef pair<int,int> PII;
typedef pair<int,string> PIS;



int f(string state){ //估计距离
int res = 0;
for (int i = 0; i < state.size(); i ++ )
if (state[i] != 'x')
{
int t = state[i] - '1';
res += abs(i / 3 - t / 3) + abs(i % 3 - t % 3);
}
return res;

}
string bfs(string start){
unordered_map<string,int> dist; //状态距离起点的距离
unordered_map<string,pair<string,char> >prev;//记录是从哪一个状态转,什么方式移过来的
priority_queue<PIS,vector<PIS>,greater<PIS> >hp;//小根堆
string ed="12345678x";
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
char op[4] = {'u', 'r', 'd', 'l'};

hp.push({f(start), start});
dist[start] = 0;

while(hp.size()) {
PIS t = hp.top();
hp.pop();

string state = t.second;
if (state == ed) break;

int step = dist[state];
int x, y;
for (int i = 0; i < state.size(); i++)
if (state[i] == 'x') {
x = i / 3, y = i % 3;
break;
}
string source = state;


for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= 3 || b < 0 || b >= 3) continue;
swap(state[x * 3 + y], state[a * 3 + b]);
if (!dist.count(state) || dist[state] > step + 1) {
dist[state] = step + 1;
prev[state] = {source, op[i]};
hp.push({dist[state] + f(state), state});
}
swap(state[x * 3 + y], state[a * 3 + b]);
}
}
string res;
while (ed != start) {
res += prev[ed].second;
ed = prev[ed].first;
}
reverse(res.begin(), res.end());
return res;

}

int main(){
string g,seq;
char c;
while(cin>>c){
g+=c;
if(c!='x') seq+=c;
}
int cnt=0;
for(int i=0;i<8;i++){
for(int j=i+1;j<9;j++){
if(seq[j]<seq[i]) cnt++;
}
}
if(cnt%2){
cout<<"unsolvable"<<endl;
return 0;
}
cout<<bfs(g)<<endl;

return 0;
}