一、题目

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

二、两种方法

该题很明显,是一个八数码的问题,就是9宫格,里面有一个空格,外加1~8的数字,任意一种情况,如果能通过移动空格使数码组成

1 2 3
4 5 6
7 8 0

的形式,就输出变换的序列,如果不能,输出unsolvable.

逆向$BFS$+康托展开

1.什么是康托展开

https://zh.wikipedia.org/wiki/%E5%BA%B7%E6%89%98%E5%B1%95%E5%BC%80

$wiki$讲解比较好。

有了康托展开,你可能会有疑问,要它有何用?但如果你联系一下$hash$函数,康托展开能够保证每一种八数码的情况都能够用一个整型数字唯一表示,这样是不是就好理解了?

2.逆向BFS

为了求出它的变换序列,我们可以逆向思维想一下,如果我从最终结果出发去变换,然后把路途中的每一种情况都记录下来(有了康托展开对八数码进行$hash$,会很方便),记录变换路径,然后对输入的其实条件直接进行输出就可以了。

这就是逆向$BFS$的解决方案。

这里需要注意的是,如果用STL的queue以及string,可能会造成超内存。解决办法就是用个数组模拟即可。

 #include <vector>
#include <cstdio>
#include <iostream>
#include <fstream>
#include <queue>
#include <cstring> using namespace std; const int MAXN = ;
const int fac[] = {, , , , , , , , , }; //factorial
const int dx[] = {-, , , };
const int dy[] = {, , , -};
const char op[] = "dulr"; //operation
vector<char> Path[MAXN]; //记录路径
bool Visit[MAXN]; //标记数组
struct Node
{
int S[]; //二维的数码表一维表示(下标从1开始)
int loc; //9 = x loaction
int cat; //对应的康拓展开值
};
Node Q[MAXN];
int Cantor(int s[])
{
int t, ans = ;
for(int i = ; i < ; i++)
{
t = ;
for(int j = i+; j < ; j++)
{
if(s[j] < s[i])
t++;
}
ans += t*fac[-i-];
}
return ans;
} void BFS()
{
memset(Visit, , sizeof(Visit));
int x, y, Cnt = , Rea = ;
Node cur, t;
for(int i = ; i < ; i++)
cur.S[i] = i+;
cur.S[] = ;
cur.loc = ;
cur.cat = Cantor(cur.S);
//Path[cur.cat] = "";
Visit[cur.cat] = ;
Q[Cnt++] = cur; while(Rea < Cnt)
{ t = Q[Rea];
Rea++;
for(int i = ; i < ; i++)
{
x = t.loc/ + dx[i];
y = t.loc% + dy[i];
if(x < || x > || y < || y > )
continue;
cur = t; //**
cur.loc = x*+y;
cur.S[t.loc] = t.S[cur.loc]; //交换
cur.S[cur.loc] = ; //X
cur.cat = Cantor(cur.S);
if(!Visit[cur.cat])
{
Visit[cur.cat] = ;
Path[cur.cat] = Path[t.cat];
Path[cur.cat].push_back(op[i]);
//Path[cur.cat] = op[i] + Path[t.cat];
Q[Cnt++] = cur; }
}
}
} int main()
{
//freopen("input.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
int s[];
char c[];
BFS(); while(scanf("%s", c)!=EOF)
{
if(c[] == 'x')
s[] = ;
else
s[] = c[] - '';
for(int i = ; i < ; i++)
{
scanf("%s", c);
if(c[] == 'x')
s[i] = ;
else
s[i] = c[] - '';
}
int Cat = Cantor(s);
if(Visit[Cat])
{
for(int i = Path[Cat].size()-; i >= ; i--)
printf("%c", Path[Cat][i]);
printf("\n");
}
else
printf("unsolvable\n");//cout << "unsolvable" << endl;
}
return ;
}

AC代码

3.A*

http://www.cnblogs.com/me-sa/archive/2010/05/18/A-Star-Pathfinding-for-Beginners.html

基本A*算法的入门讲解都是这个。其实当你认真体会后,A*算法的关键就是F=G+H中的G,H如何算的问题,其他的与搜索大同小异,因为有了G,H,就可以有目的性的去搜索,也就是启发式搜索。(仅个人理解)

这个题目里,求H依然采用的是曼哈顿距离,即每个每个数从当前位置到它最终位置的曼哈顿距离。

这里,还用到了小技巧,就是逆序数,当在满足上述约定的八数码问题中,空格与相邻棋子的交换不会改变棋局中棋子数列的逆序数的奇偶性。因为最终情况的逆序数是偶数,所以要保证每次搜索过程中逆序数都是偶数。这样就达到了剪枝。

需要注意的是,求逆序数,无论空格是代表的0还是9,都不要考虑进去。

推广一下:对于N*M数码问题,空白在同一行交换不会导致奇偶性互变;上下行交换,如果列为奇数,则不会导致奇偶性互变;如果列数为偶数,则会导致奇偶性互变,所以此时还要考虑上下行交换的次数,综合得出答案。

 /*逆向BFS*/
/*
#include <vector>
#include <cstdio>
#include <iostream>
#include <fstream>
#include <queue>
#include <cstring> using namespace std; const int MAXN = 370000;
const int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; //factorial
const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, 1, -1};
const char op[] = "dulr"; //operation
vector<char> Path[MAXN]; //记录路径
bool Visit[MAXN]; //标记数组
struct Node
{
int S[9]; //二维的数码表一维表示(下标从1开始)
int loc; //9 = x loaction
int cat; //对应的康拓展开值
};
Node Q[MAXN];
int Cantor(int s[])
{
int t, ans = 1;
for(int i = 0; i < 9; i++)
{
t = 0;
for(int j = i+1; j < 9; j++)
{
if(s[j] < s[i])
t++;
}
ans += t*fac[9-i-1];
}
return ans;
} void BFS()
{
memset(Visit, 0, sizeof(Visit));
int x, y, Cnt = 0, Rea = 0;
Node cur, t;
for(int i = 0; i < 8; i++)
cur.S[i] = i+1;
cur.S[8] = 0;
cur.loc = 8;
cur.cat = Cantor(cur.S);
//Path[cur.cat] = "";
Visit[cur.cat] = 1;
Q[Cnt++] = cur; while(Rea < Cnt)
{ t = Q[Rea];
Rea++;
for(int i = 0; i < 4; i++)
{
x = t.loc/3 + dx[i];
y = t.loc%3 + dy[i];
if(x < 0 || x > 2 || y < 0 || y > 2)
continue;
cur = t; //**
cur.loc = x*3+y;
cur.S[t.loc] = t.S[cur.loc]; //交换
cur.S[cur.loc] = 0; //X
cur.cat = Cantor(cur.S);
if(!Visit[cur.cat])
{
Visit[cur.cat] = 1;
Path[cur.cat] = Path[t.cat];
Path[cur.cat].push_back(op[i]);
//Path[cur.cat] = op[i] + Path[t.cat];
Q[Cnt++] = cur; }
}
}
} int main()
{
//freopen("input.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
int s[10];
char c[2];
BFS(); while(scanf("%s", c)!=EOF)
{
if(c[0] == 'x')
s[0] = 0;
else
s[0] = c[0] - '0';
for(int i = 1; i < 9; i++)
{
scanf("%s", c);
if(c[0] == 'x')
s[i] = 0;
else
s[i] = c[0] - '0';
}
int Cat = Cantor(s);
if(Visit[Cat])
{
for(int i = Path[Cat].size()-1; i >= 0; i--)
printf("%c", Path[Cat][i]);
printf("\n");
}
else
printf("unsolvable\n");//cout << "unsolvable" << endl;
}
return 0;
}
2 3 4 1 5 0 7 6 8
2 3 0 1 5 4 7 6 8
90747
2 3 4 1 5 0 7 6 8
92307 */ /* A* */ #include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <fstream> using namespace std; const int MAXN = ;
const int fac[] = {, , , , , , , , , }; //factorial
const int dx[] = {, , , -};
const int dy[] = {, -, , };
const char op[] = "rldu"; //operation
const int Aim = ;
int Pre[MAXN];
char Vp[MAXN];
bool Visit[MAXN];
struct Node
{
int s[];
int cat;
int g, h;
int loc; bool operator < (const Node &t)const
{
if(h == t.h)
return g > t.g;
return h > t.h;
}
}; int getCator(const int s[])
{
int t, ans = ;
for(int i = ; i < ; i++)
{
t = ;
for(int j = i+; j < ; j++)
{
if(s[j] < s[i])
t++;
}
ans += t*fac[-i-];
}
return ans; } int getH(const int s[])
{
int x, y, x0, y0, ans = ;
for(int i = ; i < ; i++)
{
if(s[i])
{
x0 = i/, y0 = i%;
x = (s[i]-)/, y = (s[i]-)%; //这里要注意
ans += abs(x-x0) + abs(y-y0); //曼哈顿距离
}
}
return ans;
} bool judge(const int S[]) //判断逆序对数是否为偶数
{
int ans = ;
for(int i = ; i < ; i++)
{
for(int j = i+; j < ; j++)
{
if( S[j] && S[i] && S[j] < S[i] )
ans++;
}
}
if(ans% == )
return true;
else
return false;
} void astar(Node cur)
{
memset(Visit, , sizeof(Visit));
memset(Pre, -, sizeof(Pre));
int x, y;
priority_queue<Node> PQ;
PQ.push(cur);
Visit[cur.cat] = ;
Pre[cur.cat] = -; while(!PQ.empty())
{
Node t = PQ.top();
PQ.pop();
for(int i = ; i < ; i++)
{
x = t.loc/ + dx[i];
y = t.loc% + dy[i];
if(x < || x > || y < || y > )
continue;
cur = t;
cur.loc = x* + y;
cur.s[t.loc] = t.s[cur.loc];
cur.s[cur.loc] = ;
cur.cat = getCator(cur.s); if(Visit[cur.cat] == && judge(cur.s))
{ Visit[cur.cat] = ;
cur.h = getH(cur.s);
cur.g++;
Pre[cur.cat] = t.cat;
Vp[cur.cat] = op[i];
if(cur.cat == Aim)
return;
PQ.push(cur);
}
}
}
} void Print()
{
int c = Aim;
string ans = "";
while(Pre[c] != -)
{
ans = Vp[c]+ans;
c = Pre[c];
}
cout << ans << endl;
} int main()
{
//freopen("input.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
Node cur;
char c[];
while(scanf("%s", c)!=EOF)
{
if(c[] == 'x')
{
cur.s[] = ;
cur.loc = ;
}
else
cur.s[] = c[] - '';
for(int i = ; i < ; i++)
{
scanf("%s", c);
if(c[] == 'x')
{
cur.s[i] = ;
cur.loc = i;
}
else
cur.s[i] = c[] - '';
} if(!judge(cur.s))
{
printf("unsolvable\n");
continue;
}
cur.cat = getCator(cur.s);
cur.g = , cur.h = getH(cur.s);
astar(cur);
Print();
}
return ;
}

AC代码

HDU_1043 Eight 【逆向BFS + 康托展开 】【A* + 康托展开 】的更多相关文章

  1. HDU1043 Eight(八数码:逆向BFS打表+康托展开)题解

    Eight Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Sub ...

  2. 康托展开&逆康托展开学习笔记

    啊...好久没写了...可能是最后一篇学习笔记了吧 题目大意:给定序列求其在全排列中的排名&&给定排名求排列. 这就是康托展开&&逆康托展开要干的事了.下面依次介绍 一 ...

  3. hdu1043 经典的八数码问题 逆向bfs打表 + 逆序数

    题意: 题意就是八数码,给了一个3 * 3 的矩阵,上面有八个数字,有一个位置是空的,每次空的位置可以和他相邻的数字换位置,给你一些起始状态 ,给了一个最终状态,让你输出怎么变换才能达到目的. 思路: ...

  4. 康拓展开 & 逆康拓展开 知识总结(树状数组优化)

    康拓展开 : 康拓展开,难道他是要飞翔吗?哈哈,当然不是了,康拓具体是哪位大叔,我也不清楚,重要的是 我们需要用到它后面的展开,提到展开,与数学相关的,肯定是一个式子或者一个数进行分解,即 展开. 到 ...

  5. hdoj1043 Eight(逆向BFS+打表+康拓展开)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1043 思路: 由于自己对康拓展开用的太少,看到这个题没想到康拓展开,最开始打算直接转换为数字,但太占内 ...

  6. 康托(Cantor)展开

    直接进入正题. 康托展开 Description 现在有"ABCDEFGHIJ”10个字符,将其所有的排列中按字典序排列,给出任意一种排列,说出这个排列在所有的排列中是第几小的? Input ...

  7. Luogu5367 【模板】康托展开 (康拓展开)

    \(n^2\)暴力 #include <iostream> #include <cstdio> #include <cstring> #include <al ...

  8. 逆向bfs搜索打表+康拓判重

    HDU 1043八数码问题 八数码,就是1~8加上一个空格的九宫格,这道题以及这个游戏的目标就是把九宫格还原到从左到右从上到下是1~8然后最后是空格. 没了解康托展开之前,这道题怎么想都觉得很棘手,直 ...

  9. 【算法系列学习三】[kuangbin带你飞]专题二 搜索进阶 之 A-Eight 反向bfs打表和康拓展开

    [kuangbin带你飞]专题二 搜索进阶 之 A-Eight 这是一道经典的八数码问题.首先,简单介绍一下八数码问题: 八数码问题也称为九宫问题.在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的 ...

随机推荐

  1. opennebula 开发记录

    /app/opennebula/var//datastores/1/12933297f0ffeba3e55bbccabcb3153b to 127.0.0.1:/app/opennebula/data ...

  2. Python PyPI中国镜像

    from:http://blog.makto.me/post/2012-11-01/pypi-mirror from:http://www.pypi-mirrors.org/ from:http:// ...

  3. python if __name__ == '__main__' 作用

    转载:https://stackoverflow.com/questions/419163/what-does-if-name-main-do When your script is run by p ...

  4. MySQL——explain性能分析的使用

    用法:explain sql语句: id:查询的序号. ref:进行连接查询时,表得连接关系.可以通过上图看出. select_type:select查询的类型,主要是区别普通查询和联合查询.子查询之 ...

  5. xml解析中的DOM和SAX的区别

    面试题:DMO和SAX的区别? DOM解析的优点:增删查改操作方便,缺点:占用内存较大,不适合解析大的XML文件: SAX解析的优点:占用内存小,解析快:缺点:不适合增删查改:

  6. python中的os模块几个常用的方法

    os.getcwd() 得到当前工作目录,即当前python脚本工作的目录路径 os.remove(file):删除一个文件 os.mkdir(name):创建目录 os.path.exists(na ...

  7. Activity和Fragment的生命周期

  8. 比特币技术之迷-Transaction 交换

    Transaction 交换每个客户端都会广播本地生成的Transaction,并转给来自其它节点的Transaction,本文主要描述Transaction之间的交换与流转过程. 大家也可以阅读以下 ...

  9. 小议C#接口的隐式与显示实现

    小弟不才,各位大牛嘴下留情... 一.对于继承类里头有相同方法时候,用接口方式去调用,会优先查走显式接口方法 例如 public interface IA { void Test(); } publi ...

  10. angular OnChange事件

    import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; @Component({ sel ...