HDU1043_Eight_A*算法&康托展开

来源:互联网 发布:小班防火知多少课件 编辑:程序博客网 时间:2024/05/16 08:07

题意

八数码问题,不必赘述。

思路

BFS 在 POJ 过,然而 HDU T成狗。
正解为 A* ,加上 康托展开 压缩状态。

A*算法

A* 算法的核心是公式 f = g + h。其中,g 起始状态到当前状态的距离,h 当前状态到目标状态的估计距离。
BFS 是 A* 的一种特殊状况, h 恒等于 0。

h 的估计

如果h(n)< d(n)到目标状态的实际距离,这种情况下,搜索的点数多,搜索范围大,效率低。但能得到最优解。
如果h(n)=d(n),即距离估计h(n)等于最短距离,那么搜索将严格沿着最短路径进行, 此时的搜索效率是最高的。
如果 h(n)>d(n),搜索的点数少,搜索范围小,效率高,但不能保证得到最优解。

实现起来与 BFS 很相似,要用到优先队列。
A* 算法的缺点是占用内存随问题的规模指数增长。据说 IDA* 能弥补这一点,以后再学。
可能是因为这个原因,有一个迷之 MLE 的地方。具体见 AC 代码中注释的注意点把。

本题中把状态中每个数字到正确位置的欧氏距离和作为估计值。

康托展开

就是用从小到大的次序序号代表长度为 n 的排列,可以把状态数压缩到 n!。
求每个排列的序号可以求小于它的排列的个数,用数位DP的思想。枚举相等的位,以下的位便不受限制。特别的,第 t 位 (n - 1 - t)! * k ,其中 k 是 t 位之后的小于第 t 为的数字的个数。

题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=1043

AC代码

#include<iostream>#include<cstdio>#include<algorithm>#include<queue>#include<cstdlib>#include<cstring>using namespace std;const int maxn = 4e5 + 10;const int mx[4] = {-1, 0, 1, 0};const int my[4] = {0, 1, 0, -1};const char op[7] = "urdl";struct node                                                         //保存 A* 状态的结构体{    int f, g, h;                                                    //f = g + h    int has;                                                        //康托展开后的哈希值    int m[3][3];                                                    //保存当前状态图    int x, y;                                                       //空白格的位置    bool operator < (node b) const {return f == b.f ? g > b.g : f > b.f;}   //重载 < 号,优先队列};bool vis[maxn];                                                         //A* 的访问标记int pre[maxn];                                                          //保存路径中的前缀char chr[maxn];                                                         //保存路径中的操作int get_h(int m[][3])                   //状态中每个数字到正确位置的欧氏距离和作为估计值{    int res = 0;    for(int i= 0; i< 3; i++)        for(int j= 0; j< 3; j ++)            res += abs((m[i][j] - 1) / 3 - i) + abs((m[i][j] - 1) % 3 - j);    return res;}const int can[9] = {1,1,2,6,24,120,720,5040,40320};                     //9个数的康托展开int Cantor(int m[][3]){    int res = 0, k = 0;    int temp[10];                                               //保存到一维数组中便于计算    for(int i= 0; i< 3; i++)        for(int j= 0; j< 3; j++)            temp[k ++] = m[i][j];    for(int i= 0; i< 9; i++)                                    //类比数位DP的原理    {        k = 0;        for(int j= i + 1; j< 9; j++)            if(temp[j] < temp[i]) k ++;        res += can[9 - 1 - i] * k;    }    return res;}void print(int x)                                               //输出路径{    if(pre[x] == -1) return;    print(pre[x]);    printf("%c", chr[x]);}void Astar(node s)                                              //A* 搜索{    if(s.has == 0)                                              //起始状态就是目标状态    {        printf("\n");        return;    }    priority_queue<node> qu;                                    //从初始状态出发    memset(vis, false, sizeof vis);    vis[s.has] = true;    qu.push(s);    pre[s.has] = -1;    while(qu.size())    {        node v = qu.top();        qu.pop();        for(int i= 0; i< 4; i++)        {            int xx = v.x + mx[i], yy = v.y + my[i];            if(xx < 0 || yy < 0 || xx >= 3 || yy >= 3) continue;            node u = v;            swap(u.m[u.x][u.y], u.m[xx][yy]);            u.has = Cantor(u.m);            if(vis[u.has]) continue;                            //判断当前状态是否访问过            vis[u.has] = true;//注意,在这里做访问标记,不是从队列中取出的时候,借助dijkstra理解            pre[u.has] = v.has, chr[u.has] = op[i];             //记录路径            if(u.has == 0)                                      //到达最终状态,注意点            {                print(0);                printf("\n");                return;            }            u.g ++;                                             //填写状态信息            u.h = get_h(u.m);            u.f = u.g + u.h;            u.x = xx, u.y = yy;            qu.push(u);                                         //加入队列        }    }}int main(){    char str[30];    node s;    while(gets(str))    {        for(int i= 0, j = 0; i< 9 && str[j] != '\n'; j++)               //建立起点的状态        {            if(str[j] == ' ') continue;            else if(str[j] == 'x') s.m[i / 3][i % 3] = 9, s.x = i / 3, s.y = i % 3;            else s.m[i / 3][i % 3] = str[j] - '0';            i ++;        }        s.g = 0;        s.h = get_h(s.m);        s.f = s.g + s.h;        s.has = Cantor(s.m);        int k = 0;                                          //通过逆序数的奇偶判断是否可解        for(int i= 0; i< 9; i++)            for(int j= 0; j< i; j++)                if(s.m[j / 3][j % 3] != 9 && s.m[i / 3][i % 3] < s.m[j / 3][j % 3]) k ++;        if(k & 1) printf("unsolvable\n");        else Astar(s);                                      //注意点    }    return 0;}
原创粉丝点击