POJ - 1077 Eight : 八数码 -- 哈唏 康托展开 双向bfs A* IDA*

前言

做完这道题,感觉整个人都完整了。
这道题所能涉及到的知识点特别多,列举如下:

  • 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 4

5 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 4

5 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 3

x 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.csdn.net/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;
}

 以上.

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值