HDU1043_Eight_A*算法&康托展开

题意

八数码问题,不必赘述。

思路

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
#include
#include
#include
#include
#include

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 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;
}

你可能感兴趣的:(搜索)