HDU_1043 Eight 【逆向BFS + 康托展开 】【A* + 康托展开 】
一、题目
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* + 康托展开 】的更多相关文章
- HDU1043 Eight(八数码:逆向BFS打表+康托展开)题解
Eight Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Sub ...
- 康托展开&逆康托展开学习笔记
啊...好久没写了...可能是最后一篇学习笔记了吧 题目大意:给定序列求其在全排列中的排名&&给定排名求排列. 这就是康托展开&&逆康托展开要干的事了.下面依次介绍 一 ...
- hdu1043 经典的八数码问题 逆向bfs打表 + 逆序数
题意: 题意就是八数码,给了一个3 * 3 的矩阵,上面有八个数字,有一个位置是空的,每次空的位置可以和他相邻的数字换位置,给你一些起始状态 ,给了一个最终状态,让你输出怎么变换才能达到目的. 思路: ...
- 康拓展开 & 逆康拓展开 知识总结(树状数组优化)
康拓展开 : 康拓展开,难道他是要飞翔吗?哈哈,当然不是了,康拓具体是哪位大叔,我也不清楚,重要的是 我们需要用到它后面的展开,提到展开,与数学相关的,肯定是一个式子或者一个数进行分解,即 展开. 到 ...
- hdoj1043 Eight(逆向BFS+打表+康拓展开)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1043 思路: 由于自己对康拓展开用的太少,看到这个题没想到康拓展开,最开始打算直接转换为数字,但太占内 ...
- 康托(Cantor)展开
直接进入正题. 康托展开 Description 现在有"ABCDEFGHIJ”10个字符,将其所有的排列中按字典序排列,给出任意一种排列,说出这个排列在所有的排列中是第几小的? Input ...
- Luogu5367 【模板】康托展开 (康拓展开)
\(n^2\)暴力 #include <iostream> #include <cstdio> #include <cstring> #include <al ...
- 逆向bfs搜索打表+康拓判重
HDU 1043八数码问题 八数码,就是1~8加上一个空格的九宫格,这道题以及这个游戏的目标就是把九宫格还原到从左到右从上到下是1~8然后最后是空格. 没了解康托展开之前,这道题怎么想都觉得很棘手,直 ...
- 【算法系列学习三】[kuangbin带你飞]专题二 搜索进阶 之 A-Eight 反向bfs打表和康拓展开
[kuangbin带你飞]专题二 搜索进阶 之 A-Eight 这是一道经典的八数码问题.首先,简单介绍一下八数码问题: 八数码问题也称为九宫问题.在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的 ...
随机推荐
- swfupload上传文件数量限制之setStats()
使用swfupload仿赶集的图片上传 SWFUpload是一个基于flash与javascript的客户端文件上传组件. handlers.js文件 完成文件入列队(fileQueued) → 完成 ...
- 680. Valid Palindrome II 对称字符串-可删字母版本
[抄题]: Given a non-empty string s, you may delete at most one character. Judge whether you can make i ...
- dpdk中kni模块
一,什么是kni,为什么要有kni Kni(Kernel NIC Interface)内核网卡接口,是DPDK允许用户态和内核态交换报文的解决方案,模拟了一个虚拟的网口,提供dpdk的应用程序和lin ...
- c语言学习笔记 if语句的条件判断
可能经常会看到错误的if语句示范,比如这样的: if(a=6) { printf("hello"); } if语句块执行的条件是if条件的运算结果不是0则执行if语句块. a=6这 ...
- 编写高质量代码改善C#程序的157个建议——建议39:了解委托的实质
建议39:了解委托的实质 理解C#中的委托需要把握两个要点: 1)委托是方法指针. 2)委托是一个类,当对其进行实例化的时候,要将引用方法作为它的构造方法的参数. 设想这样一个场景:在点对点文件传输过 ...
- 树形DP-----HDU4003 Find Metal Mineral
Find Metal Mineral Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65768/65768 K (Java/Other ...
- java并发机制的底层实现原理
volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的"可见性".可见性是说当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值. vola ...
- DPF.Android.Native.Components.v2.8.1 for delphi xe6 使用DPFJAlertDialog遇到的问题
使用DPFJAlertDialog控件时发现DPFJAlertDialog1Click不能捕获到对话框到底按了那个按键,上网搜索后找到了解决方法: 打开DPF.Android.JAlertDialog ...
- CH收藏的书
论语 道德经 墨子
- Selenium API(二)
1.定位一组元素 WebDriver提供了8种定位一组元素的方法. driver.find_elements_by_css_selector() driver.find_elements_by_tag ...