一、题目

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. 关于使用sessionStorage报SecurityError错误的问题

    localStorage 永久保存 不同页面和标签页可以共享 关闭浏览器不会清除 sessionStorage 会话保存 不同页面和标签页不能共享 关闭浏览器会清除 遇到的问题:在firefox中报S ...

  2. centos 安装mongodb3.0

    一.添加yum源 vi /etc/yum.repos.d/mongodb-org-3.0.repo 请添加如下配置(64位系统): [mongodb-org-3.0] name=MongoDB Rep ...

  3. HTTP防盗链与反防盗链

    HTTP防盗链 通过上一次,我没对HTTP请求不再那么陌生了.防盗链无非就是别人来请求自己网站的信息,用于其他网站,那么如果我们能识别请求是来自那个网站,如果是外网,那么就重定向等其他处理.但在web ...

  4. Python基础入门-函数实战登录功能

    ''' 函数实战: .加法计算器 .过滤器 .登录功能实战 ''' def add(a,b): return a+b def login_order(): return 'asdfasdfdasfad ...

  5. Instruments Tutorial for iOS: How To Debug Memory Leaks【转】

    If you're new here, you may want to subscribe to my RSS feed or follow me on Twitter. Thanks for vis ...

  6. 策略与计费控制(PCC)流程与信令流程

    该文为3GPP TS23.203-be0 条款6-7译文 策略与计费控制(PCC)流程[^4] IP-CAN 会话有三种显著的场景: 无网关控制会话需求,不会出现网关控制建立 需要网关控制会话支持:B ...

  7. 简单工厂(Simple Factory)模式

    工厂模式专门负责将大量有共同接口的类实例化.工厂模式可以动态决定将哪一个类实例化,不必事先知道每次要实例化哪一个类.工厂模式有以下几种形态: 简单工厂(Simple Factory)模式 工厂方法(F ...

  8. Python中多使用迭代器

    英文原文出处:Use More Iterators 本文介绍将代码转换为使用迭代器的原因和实用技巧. 我最喜欢的Python语言的特色之一是生成器,它们是非常有用的,然而当阅读开源代码时,我很少遇到它 ...

  9. switch case 判断是否为按钮、设置属性 Load Foreach 绑定事件

    private void button9_Click(object sender, EventArgs e) { foreach (Control CT in this.Controls) {//判断 ...

  10. ecliplse启动慢的优化方法(亲测有效)

    本人使用的ecliplse IDE 版本是: Version: Photon Release (4.8.0)Build id: 20180619-1200 我的电脑配置是 Ecliplse启动差不多一 ...