前言
做完这道题,感觉整个人都完整了。
这道题所能涉及到的知识点特别多,列举如下:
- STL判重
- Hash判重
- 康托展开
- 双向bfs
- A*
- IDA*
每一种知识点分别对应不同的版本,下面的题解按照以上顺序给出各个版本。
另外有个比较好的博客,可以根据这个博客给出的顺序一步一步实现代码,再此感谢此大牛博主。
点此转到 -> 八数码的八境界http://www.cnblogs.com/goodness/archive/2010/05/04/1727141.html
题目点此跳转
The 15-puzzle has been around for over 100 years; even if you don’t know it by that name, you’ve seen it. It is constructed with 15 sliding tiles, each with a number from 1 to 15 on it, and all packed into a 4 by 4 frame with one tile missing. Let’s call the missing tile ‘x’; the object of the puzzle is to arrange the tiles so that they are ordered as:
1 2 3 45 6 7 8
9 10 11 12
13 14 15 x
where the only legal operation is to exchange ‘x’ with one of the tiles with which it shares an edge. As an example, the following sequence of moves solves a slightly scrambled puzzle:
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 45 6 7 8 5 6 7 8 5 6 7 8 5 6 7 8
9 x 10 12 9 10 x 12 9 10 11 12 9 10 11 12
13 14 11 15 13 14 11 15 13 14 x 15 13 14 15 x
r-> d-> r->
The letters in the previous row indicate which neighbor of the ‘x’ tile is swapped with the ‘x’ tile at each step; legal values are ‘r’,’l’,’u’ and ‘d’, for right, left, up, and down, respectively.
Not all puzzles can be solved; in 1870, a man named Sam Loyd was famous for distributing an unsolvable version of the puzzle, and frustrating many people. In fact, all you have to do to make a regular puzzle into an unsolvable one is to swap two tiles (not counting the missing ‘x’ tile, of course).
In this problem, you will write a program for solving the less well-known 8-puzzle, composed of tiles on a three by three arrangement.
Input
You will receive a description of a configuration of the 8 puzzle. The description is just a list of the tiles in their initial positions, with the rows listed from top to bottom, and the tiles listed from left to right within a row, where the tiles are represented by numbers 1 to 8, plus ‘x’. For example, this puzzle
1 2 3x 4 6
7 5 8
is described by this list:
1 2 3 x 4 6 7 5 8
Output
You will print to standard output either the word “unsolvable”, if the puzzle has no solution, or a string consisting entirely of the letters ‘r’, ‘l’, ‘u’ and ‘d’ that describes a series of moves that produce a solution. The string should include no spaces and start at the beginning of the line.
Sample Input
2 3 4 1 5 x 7 6 8
Sample Output
ullddrurdllurdruldr
题意
题目意思就是八数码问题。
编号为1~8的8个正方形滑块被摆成3行3列(有一个格子留空,用x表示)。每次可以把与空格相邻的滑块(有公共边才算相邻)移到空格中,而它原来的位置就成为了新的空格。给定初始局面(用x表示空格),目标局面为(12345678x),你的任务是输出最少步数的移动方案(任输一组即可)。
版本一 – bfs+STL
这个版本是最无脑版的,直接一个裸的bfs,用set作为vis数组记录一下是否访问过来判重即可,但是肯定会超时。
关于代码的几点注释
state数组:实时保存每一种状态,每种状态都是一个长度为9的数组
set < int > vis:判重数组,已经访问过的状态都会加入此表
check(x)函数:判重操作 – 如果x未访问,则将其加入已访问表,并返回0, 否则返回1
queue< int > q:只维护每个状态的下标,根据下标在vis 里找即可
pre数组: 存储前趋结点 –输出答案用
ans数组:存储当前路径上的方向 – 输出答案用
#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <cstring>
#include <cstdio>
#include <cmath>
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
#define OT freopen("out.txt", "w", stdout);
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int maxn = 1e6 + 100;
const int INF = 0x7fffffff;
const int dir[4][2] = {-1,0,1,0,0,-1,0,1};
char Dir[5] = "udlr";
char c, res[maxn];
int state[maxn][9], aim[9], tmp[9], sid;
int pre[maxn], ans[maxn];
set<int> vis;
void init() { sid = 1; vis.clear(); }
void ot(int t) {
int rid = 0;
while(t != 1) {
res[rid++] = Dir[ans[t]];
t = pre[t];
}
for(int i = rid-1; i >= 0; --i) printf("%c", res[i]);printf("\n");
}
bool check(int x) {
int t = 0;
for(int i = 0; i < 9; ++i) t = t*10 + state[x][i];
if(vis.count(t)) return 1;
vis.insert(t); return 0;
}
void bfs() {
queue<int> q; q.push(1);
while(!q.empty()) {
int t = q.front(); q.pop();
if(memcmp(state[t], aim, sizeof(aim)) == 0) { ot(t); return; }
for(int i = 0; i < 4; ++i) {
int p = 0;
while(state[t][p] != 0) ++p;
int tx = p/3 + dir[i][0], ty = p%3 + dir[i][1];
int tp = tx*3 + ty;
if(tx >= 0 && tx < 3 && ty >= 0 && ty < 3) {
memcpy(tmp, state[t], sizeof(tmp));
tmp[p] = tmp[tp]; tmp[tp] = 0;
memcpy(state[++sid], tmp, sizeof(state[sid]));
if(!check(sid)) { //not visited then set visited
q.push(sid);
pre[sid] = t; ans[sid] = i;
}
}
}
}
}
int main() {
#ifdef _LOCAL
IN;//OT;
#endif // _LOCAL
init();
for(int i = 0; i < 9; ++i) {
cin >> c;
if(c == 'x') state[sid][i] = 0;
else state[sid][i] = c-'0';
aim[i] = (i+1)%9;
}
bfs();
return 0;
}
版本二 – bfs+哈唏
现在可以根据上一版本来修改一下判重的操作,由于STL的效率有限,我们不妨使用高效的哈唏表来记录每一个状态,而哈唏的办法也特别简单,就是将九位数组成一个九位的整数,然后对一个大于9!的数进行取模,即可以将结果保存在哈唏表里了。这里使用哈唏链解决冲突。
将下面的函数:
int head[maxn], nxt[maxn];
void init() { sid = 1; met(head, 0); }
bool check(int x) {
int t = 0;
for(int i = 0; i < 9; ++i) t = t*10 + state[x][i];
t %= maxn;
int i = head[t];
while(i != 0) {
if(memcmp(state[i], state[x], sizeof(state[x])) == 0) return 1;
i = nxt[i];
}
nxt[x] = head[t]; head[t] = x; return 0;
}
替换掉上一版的check函数,其他的不动即可,这里仅仅改变了判重的方法,但是交上去不会超时,我的时间是250ms。
完整的第一个可以ac的代码如下:
#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <cstring>
#include <cstdio>
#include <cmath>
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
#define OT freopen("out.txt", "w", stdout);
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int maxn = 1e6 + 100;
const int INF = 0x7fffffff;
const int dir[4][2] = {-1,0,1,0,0,-1,0,1};
char Dir[5] = "udlr";
char c, res[maxn];
int state[maxn][9], aim[9], tmp[9], sid;
int pre[maxn], ans[maxn];
int head[maxn], nxt[maxn];
void init() { sid = 1; met(head, 0); }
void ot(int t) {
int rid = 0;
while(t != 1) {
res[rid++] = Dir[ans[t]];
t = pre[t];
}
for(int i = rid-1; i >= 0; --i) printf("%c", res[i]);printf("\n");
}
bool check(int x) {
int t = 0;
for(int i = 0; i < 9; ++i) t = t*10 + state[x][i];
t %= maxn;
int i = head[t];
while(i != 0) {
if(memcmp(state[i], state[x], sizeof(state[x])) == 0) return 1;
i = nxt[i];
}
nxt[x] = head[t]; head[t] = x; return 0;
}
void bfs() {
queue<int> q; q.push(1);
while(!q.empty()) {
int t = q.front(); q.pop();
if(memcmp(state[t], aim, sizeof(aim)) == 0) { ot(t); return; }
for(int i = 0; i < 4; ++i) {
int p = 0;
while(state[t][p] != 0) ++p;
int tx = p/3 + dir[i][0], ty = p%3 + dir[i][1];
int tp = tx*3 + ty;
if(tx >= 0 && tx < 3 && ty >= 0 && ty < 3) {
memcpy(tmp, state[t], sizeof(tmp));
tmp[p] = tmp[tp]; tmp[tp] = 0;
memcpy(state[++sid], tmp, sizeof(state[sid]));
if(!check(sid)) { //not visited then set visited
q.push(sid);
pre[sid] = t;
ans[sid] = i;
}
}
}
}
}
int main() {
#ifdef _LOCAL
IN;//OT;
#endif // _LOCAL
init();
for(int i = 0; i < 9; ++i) {
cin >> c;
if(c == 'x') state[sid][i] = 0;
else state[sid][i] = c-'0';
aim[i] = (i+1)%9;
}
bfs();
return 0;
}
版本三 – 康托展开
对于我们的判重,我们不妨从源头思考一下,之所以我们需要用STL或者hash去维护访问表,是因为如果我们直接用一个9维数组,或者开一下下标可以到9位数的一位数组,那内存开销是极大的,是高达
99
的。
但是话说回来,按理来说所有的状态也都只是9位数的全排列而已,只可能是9!个,而不会有
99
个,中间必定有许多没有用到的内存,那么我们如果有一种方法可以将这些9位数的排列与1到9!对应起来的话,问题不就解决了吗?
而康托展开,它的功能是可以确定一个排列在全排列中的位置,这正是我们想要的。
关于康托展开,为了保证此篇博客的纯正性,我另开了一篇专门介绍康托展开和康托逆展开的博客,请读者屈尊移驾此地点此跳转 – 康托展开http://blog.youkuaiyun.com/a27038/article/details/75665531
有了康托展开的知识,现在我们只需要开一个vis[9!]数组,对某个状态进行康托展开后像以前使用vis一样使用即可判重.
将如下代码:
int vis[maxn], fact[10];
void init() {
sid = 1;
met(vis, 0); fact[0] = 1;
for(int i = 1; i < 9; ++i) fact[i] = fact[i-1]*i;
}
int kt(int s[], int n) {
int ans = 0, cnt = 0;
for(int i = 0; i < n; ++i) {
cnt = 0;
for(int j = i+1; j < n; ++j) if(s[j] < s[i]) ++cnt;
ans += cnt*fact[n-i-1];
}
return ans;
}
bool check(int x) {
int code = kt(state[x], 9);
if(vis[code]) return 1;
vis[code] = 1; return 0;
}
替换原来的代码中对应的判重部分即可,其中fact[i] = i!
版本四 – 双向bfs
从现在开始呢,我们不想再在判重上下功夫了,毕竟康托他老人家都被我们折腾过了.我们不妨在搜索上下下功夫,没准还有一些改进的空间.
我们不妨将bfs改为双向bfs.
不要告诉我你连双向bfs是什么都不知道,对没错就是字面意思:从两个方向bfs.
我们只要从起点和终点同时做bfs,即可,起点走一下,然后终点走一下,直到两点相遇为止.
这样可以将时间复杂度从
bstep
优化到
bstep2
.
而双向广搜的代码也挺好实现的,你只要按照原来单向广搜的代码和变量,像吃饱套餐一样见样来两份就行了,再做一些微小的修改,也就八九不离十了.
#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <cstring>
#include <cstdio>
#include <cmath>
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
#define OT freopen("out.txt", "w", stdout);
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int maxn = 4e5 + 100;
const int INF = 0x7fffffff;
const int dir[4][2] = {-1,0,1,0,0,-1,0,1};
char Dir[5] = "udlr";
char c, res[maxn];
int state[maxn][9], aim[9], tmp[9], sid;
int pre[maxn], nxt[maxn], ans[maxn], ans2[maxn], ID[maxn];
int vis[maxn], fact[10];
void init() {
sid = 1; met(ID, 0); met(pre, 0); met(nxt, 0);
met(vis, 0); fact[0] = 1;
for(int i = 1; i < 9; ++i) fact[i] = fact[i-1]*i;
}
int kt(int s[], int n) {
int ans = 0, cnt = 0;
for(int i = 0; i < n; ++i) {
cnt = 0;
for(int j = i+1; j < n; ++j) if(s[j] < s[i]) ++cnt;
ans += cnt*fact[n-i-1];
}
return ans;
}
void ot(int t) {
int rid = 0, tmp = t;
while(t != 1) {
res[rid++] = Dir[ans[t]];
t = pre[t];
}
for(int i = rid-1; i >= 0; --i) printf("%c", res[i]); rid = 0;
while(tmp != 2) {
res[rid++] = Dir[ans2[tmp]];
tmp = nxt[tmp];
}
for(int i = 0; i < rid; ++i) printf("%c", res[i]);printf("\n");
}
int check(int x, int color) {
int code = kt(state[x], 9);
if(vis[code] != 0) return vis[code];
if(ID[code] == 0) ID[code] = x;
vis[code] = color; return 0;
}
void bfs() {
queue<int> qf, qb; qf.push(1); qb.push(2);
check(1,1); check(2,2);
while(!qf.empty() || !qb.empty()) {
if(!qf.empty()) {
int t = qf.front(); qf.pop();
for(int i = 0; i < 4; ++i) {
int p = 0;
while(state[t][p] != 0) ++p;
int tx = p/3 + dir[i][0], ty = p%3 + dir[i][1];
int tp = tx*3 + ty;
if(tx >= 0 && tx < 3 && ty >= 0 && ty < 3) {
memcpy(tmp, state[t], sizeof(tmp));
tmp[p] = tmp[tp]; tmp[tp] = 0;
memcpy(state[++sid], tmp, sizeof(state[sid]));
if(check(sid, 1) == 0) { //not visited then set visited
qf.push(sid);
pre[sid] = t; ans[sid] = i;
}
else if(check(sid, 2) == 2) {
int idx = ID[kt(state[sid], 9)];
pre[idx] = t; ans[idx] = i;
ot(idx); return;
}
}
}
}
if(!qb.empty()) {
int t = qb.front(); qb.pop();
for(int i = 0; i < 4; ++i) {
int p = 0;
while(state[t][p] != 0) ++p;
int tx = p/3 + dir[i][0], ty = p%3 + dir[i][1];
int tp = tx*3 + ty;
if(tx >= 0 && tx < 3 && ty >= 0 && ty < 3) {
memcpy(tmp, state[t], sizeof(tmp));
tmp[p] = tmp[tp]; tmp[tp] = 0;
memcpy(state[++sid], tmp, sizeof(state[sid]));
if(check(sid, 2) == 0) { //not visited then set visited
qb.push(sid);
nxt[sid] = t; ans2[sid] = i^1;
}
else if(check(sid, 1) == 1) {
int idx = ID[kt(state[sid], 9)];
nxt[idx] = t; ans2[idx] = i^1;
ot(idx); return;
}
}
}
}
}
}
int main() {
#ifdef _LOCAL
IN;//OT;
#endif // _LOCAL
init();
for(int i = 0; i < 9; ++i) {
cin >> c;
if(c == 'x') state[sid][i] = 0;
else state[sid][i] = c-'0';
state[sid+1][i] = (i+1)%9;
}
++sid; bfs();
return 0;
}
注意:
vis数组的值不再只有0和1, 还有2(表示反向)
来两份的有: 队列两份,答案分两份, 记录答案的数组分两份等
注意反着搜的方向,这里我用了一个^1,读者自行体会
相遇的条件: 正向碰到了vis等于2的, 或反向碰到了vis等于1的
这里给状态的编号应该是唯一的了,不能像单向bfs一样可以重复,因为相遇的那个结点必须一同一个id的前趋和后继,才能保证道路的畅通.所以这里多开了一个ID数组做一个双射解决问题.
版本五六七 – A*
这里为了和那位大牛的八境界对应起来,将A*分了三个版本,这里其实只作了一个,但是其他的两个与此差别不大.
双向bfs在这题的时间已经是16ms了,但是A*应该可以继续优化到0ms.
对于A*,依然是使用上面单向bfs的套路,但是多了下面的一些东西,我先把它列出来:
struct node {
int id, f, g, h;
node(){}
node(int s, int _g, int _h):id(s), g(_g), h(_h){ f = g+h*10; }
bool operator < (const node& b) const {
return f > b.f;
}
};
int getH(int sid) {
int cnt = 0;
for(int i = 0; i < 3; ++i)
for(int j = 0; j < 3; ++j) {
if(state[i*3+j] == 0) continue;
cnt += abs((state[sid][i*3+j]-1)/3-i) + abs((state[sid][i*3+j]-1)%3-j);
}
return cnt;
}
多出来的东西并不多,也很好理解,这就是A*所谓的启发式特性.
在整个A*算法里,我认为最重要的就是h了, h是从当前状态到目标状态的一个乐观的代价估值,而当我们前面在纯bfs的时候,我们的出队顺序是按照走的步数的深度来的,就是这里的g,完全没有把h放在眼里,
而在A*算法里,我们的队列使用优先队列维护,出队规则可以由我们自己定,玩的够大吧? 这就是A*算法的魅力所在.这里我取h为目前状态到终点状态的曼哈顿距离,这是一个乐观的距离(最做距离肯定比它小),将它作为出队首要考虑的条件,它越小,说明此状态越接近目标状态,它出队的优先级就越高.
这是A*和bfs的主要区别,可以说bfs就是h = 0 的A*算法.
所以我们只要将上面的这些加进去,然后再在bfs里稍稍修改那么一丢丢东西,比如将int型的队列改为node型的优先队列,入队时更新h和g值等,基本上就算是A*算法了.
#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <cstring>
#include <cstdio>
#include <cmath>
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
#define OT freopen("out.txt", "w", stdout);
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int maxn = 4e5 + 100;
const int INF = 0x7fffffff;
const int dir[4][2] = {-1,0,1,0,0,-1,0,1};
char Dir[5] = "udlr";
char c;
int state[maxn][9], aim[9], tmp[9], sid, pre[maxn], ans[maxn], res[maxn], rid;
int head[maxn], nxt[maxn];
bool check(int x) {
int t = 0;
for(int i = 0; i < 9; ++i) t = t*10 + state[x][i];
t %= maxn;
int u = head[t];
while(u != 0) {
if(memcmp(state[x], state[u], sizeof(state[x])) == 0) return 1;
u = nxt[u];
}
nxt[x] = head[t]; head[t] = x; return 0;
}
void init() { sid = 1; met(head, 0); }
void ot(int t) {
rid = 0;
while(t != 1) {
res[rid++] = ans[t];
t = pre[t];
}
for(int i = rid-1; i >= 0; --i) printf("%c", Dir[res[i]]); printf("\n");
}
struct node {
int id, f, g, h;
node(){}
node(int s, int _g, int _h):id(s), g(_g), h(_h){ f = g+h*10; }
bool operator < (const node& b) const {
return f > b.f;
}
};
int getH(int sid) {
int cnt = 0;
for(int i = 0; i < 3; ++i)
for(int j = 0; j < 3; ++j) {
if(state[i*3+j] == 0) continue;
cnt += abs((state[sid][i*3+j]-1)/3-i) + abs((state[sid][i*3+j]-1)%3-j);
}
return cnt;
}
void bfs() {
priority_queue<node> q;
q.push(node(sid, 0, getH(sid))); check(sid);
while(!q.empty()) {
node t = q.top(); q.pop();
if(memcmp(state[t.id], aim, sizeof(aim)) == 0) { ot(t.id); return;}
for(int i = 0; i < 4; ++i) {
int p = 0;
while(state[t.id][p] != 0) ++p;
int tx = p/3 + dir[i][0], ty = p%3 + dir[i][1];
int tp = tx*3 + ty;
if(tx >= 0 && tx < 3 && ty >= 0 && ty < 3) {
memcpy(tmp, state[t.id], sizeof(tmp));
tmp[p] = tmp[tp]; tmp[tp] = 0;
memcpy(state[++sid], tmp, sizeof(tmp));
if(!check(sid)) {
q.push(node(sid,t.g+1,getH(sid)));
pre[sid] = t.id, ans[sid] = i;
}
}
}
}
}
int main() {
#ifdef _LOCAL
IN; OT;
#endif // _LOCAL
init();
for(int i = 0; i < 9; ++i) {
cin >> c;
if(c == 'x') state[sid][i] = 0;
else state[sid][i] = c-'0';
aim[i] = (i+1)%9;
}
bfs();
return 0;
}
关于版本合并:
另外一个版本只不过是h值上不同,这里我略过了那种简单的效率不怎么高的h值,直接使用了曼哈顿距离.
另外一个另外一个版本说是用小顶堆优化,我直接用的优先队列,本来优先队列就是用堆实现的,感觉也不必自己再另写堆.所以这两个版本就过了.
版本八 – IDA*
如果我们说A*是bfs的变种,那么IDA*就是dfs的变种.
我们可以想像一下,如果每一状态所能到的状态有很多,我们用bfs扩展可能连一步都扩不完,而用dfs也不知道要走到什么地方才回头.通俗点讲,当我们不能左右走到头了再往前走,或者往前走到头了再左右走,这个时候bfs和dfs就都不好使了,那么IDA*的表演时间就到了.
IDA*的出现就是为了解救顾不了深度的bfs和顾不了广度的dfs的,IDA*可以兼顾,而且实施起来也不复杂.
IDA*拿dfs开刀,每一次给dfs一个假设的搜索深度上限,当它们在这个深度范围内搜搜索不到结果之后,我再增加这个深度上限,如果有解的话,它最终一定会在某一个深度搜索到解并返回的.这就是迭代加深(Iterative Deepning),我们可以从最大深度maxd = 1 开始搜索,每次maxd+1,直到搜索到答案为值,那么我的代码应该是这样的:
#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <cstring>
#include <cstdio>
#include <cmath>
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
#define OT freopen("out.txt", "w", stdout);
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int maxn = 4e5 + 100;
const int INF = 0x7fffffff;
const int dir[4][2] = {-1,0,1,0,0,-1,0,1};
char Dir[5] = "udlr";
char c;
int s[9], aim[9], maxd, pos, ans[maxn], aid;
bool dfs(int p, int pp, int deep) {
if(deep == maxd) {
if(memcmp(s,aim, sizeof(s)) == 0) return 1;
return 0;
}
for(int i = 0; i < 4; ++i) {
int tx = p/3 + dir[i][0], ty = p%3 + dir[i][1];
int tp = tx*3 + ty;
if(tx >= 0 && tx < 3 && ty >= 0 && ty < 3) {
if(tp == pp) continue;
swap(s[p], s[tp]); ans[aid++] = i;
if(dfs(tp, p, deep+1)) return 1;
swap(s[p], s[tp]); --aid;
}
}
return 0;
}
void IDAS() {
for(maxd = 1; ; ++maxd) {
if(dfs(pos, -1, 0)) { return; }
}
}
int main() {
#ifdef _LOCAL
IN; //OT;
#endif // _LOCAL
for(int i = 0; i < 9; ++i) {
cin >> c;
if(c == 'x') s[i] = 0, pos = i;
else s[i] = c-'0';
aim[i] = (i+1)%9;
}
aid = 0; IDAS();
for(int i = 0; i < aid; ++i) printf("%c", Dir[ans[i]]);printf("\n");
return 0;
}
好短!
这个代码是一定可以输出答案的,如果有的话.因为它没有漏掉任何一层,但是它的效率却不咋地,以致于交上去ac不了,TLE了,但是可以作为一个跳板,因为严格地讲这还不是IDA*,这里只体现了ID, 并没有体现A*, 我们只要对这段代码稍稍修改一下,加上A*的元素,那么不出意外地话就会快速地ac掉吧.
我们先在maxd上下功夫.
首先maxd有一个下限,就是初始状态到目录状态的曼哈顿距离,这个步数肯定是最小的步数,不可能有小于这个步数还能走到终点状态的,我们以此为最小的搜索深度.
那么maxd每次加1吗?不是,maxd每次不用只加1,如果当前的深度内没有找到结果,那么我从当前状态中碰到的所有的状态,它们相对于目标状态的曼哈顿距离肯定是最小的maxd增量,否则也不可能通过它们达到目标状态,那么maxd的增量就是我所有遇到的状态里曼哈顿距离的最小值.
然后我们再考虑两个剪枝:
- 如果当前状态往后走一步会回到上一步,这样的肯定要剪掉.
- 如果当前状态的深度(从初始到当前走的步数)加上它的曼哈顿距离(从当前到终点需要的最小步数)比maxd还大,那么从当前状态走肯定找不到答案,必须等maxd在后面的轮次变大了才能判断.,那么这里就把它剪掉.
最后整体的代码如下:
#include <algorithm>
#include <iostream>
#include <sstream>
#include <utility>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <cstring>
#include <cstdio>
#include <cmath>
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
#define OT freopen("out.txt", "w", stdout);
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int maxn = 4e5 + 100;
const int INF = 0x7fffffff;
const int dir[4][2] = {-1,0,1,0,0,-1,0,1};
char Dir[5] = "udlr";
char c;
int s[9], aim[9], maxd, pos, ans[maxn], aid, step;
int getH() {
int ans = 0;
for(int i = 0; i < 3; ++i)
for(int j = 0; j < 3; ++j) {
if(s[i*3+j] != 0) ans += abs((s[i*3+j]-1)/3-i) + abs((s[i*3+j]-1)%3-j);
}
return ans;
}
bool dfs(int p, int pp, int deep) {
int h = getH();
step = min(step, h);
if(deep+h > maxd) return 0;
if(h == 0) return 1;
for(int i = 0; i < 4; ++i) {
int tx = p/3 + dir[i][0], ty = p%3 + dir[i][1];
int tp = tx*3 + ty;
if(tx >= 0 && tx < 3 && ty >= 0 && ty < 3) {
if(tp == pp) continue;
swap(s[p], s[tp]); ans[aid++] = i;
if(dfs(tp, p, deep+1)) return 1;
swap(s[p], s[tp]); --aid;
}
}
return 0;
}
void IDAS() {
maxd = getH();
bool ok = 0;
while(!ok) {
step = INF;
ok = dfs(pos, -1, 0);
maxd += step;
}
}
int main() {
#ifdef _LOCAL
IN; //OT;
#endif // _LOCAL
for(int i = 0; i < 9; ++i) {
cin >> c;
if(c == 'x') s[i] = 0, pos = i;
else s[i] = c-'0';
aim[i] = (i+1)%9;
}
aid = 0; IDAS();
for(int i = 0; i < aid; ++i) printf("%c", Dir[ans[i]]);printf("\n");
return 0;
}
以上.