题面

典型的\(\text{BFS}\)。

双向广搜是一种对\(\text{BFS}\)的优化,它适用于起点和终点都明确的题目。

这里给出我的双向广搜模板。

inline int bfs()//双向广搜
{
q.push(s), f[s] = 1;
q.push(t), f[t] = -1;
//队列初始化
//f数组表示这个状态时从起点来还是从终点来
//f[i]为正数就是从起点来
//f[i]为负数就是从终点来
while (!q.empty())//如果队列不为空
{
int u = q.front(); //取出队首元素
q.pop();//弹出队首
...
for (...)
{
int tmp;//tmp是可以转移的状态
...
if (!f[tmp]) //如果没有访问过这个元素
{
f[tmp] = f[u] + f[u] / abs(f[u]);//这个操作很巧妙,它可以让正数+1,负数-1
q.push(tmp);//将tmp装进队列
}
else if (f[tmp] * f[u] < 0) //乘积<0说明不是同号,来源不同
{
return abs(f[tmp] - f[u]) - 1;//直接返回答案,注意要-1
}
}
}
return -1;//返回-1说明出了问题,需要Debug
}

回到这一题,我们需要从小到大预处理处\(0 \sim 8\)的排列,然后\(\text{BFS}\)时队列里存储当前\(9\)位状态在所有排列里的下标,将\(9\)位整数转成\(3 \times 3\)的地图,找到为\(0\)的位置,将它与上下左右的四个位置交换,再将地图转成整数,放入队列中。

由于在本题中起点和终点都很明确(起点是输入的\(9\)位整数,终点是123804765),因此可以使用双向广搜优化。

完整代码:

#include <bits/stdc++.h>
#define itn int
#define gI gi using namespace std; inline int gi()
{
int f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return f * x;
} const int maxn = 3628803; int n, m, id[maxn], cnt, usd[10], f[maxn], mp[5][5], xx, yy;
queue <int> q; const int dx[] = {0, 0, 1, -1}, dy[] = {1, -1, 0, 0}; inline void getditu(int x) //将9位整数转换成3*3的地图
{
for (int i = 3; i >= 1; i-=1) //记得倒叙循环
{
for (int j = 3; j >= 1; j-=1) //记得倒叙循环
{
mp[i][j] = x % 10, x /= 10;
if (mp[i][j] == 0) xx = i, yy = j; //处理0的位置
}
}
} inline int getid() //将地图转换成9位数字
{
int uu = 0;
for (int i = 1; i <= 3; i+=1)
{
for (int j = 1; j <= 3; j+=1) uu = uu * 10 + mp[i][j];
}
return uu;
} inline bool check(int x, int y) //判断当前位置在不在地图内
{
return x >= 1 && x <= 3 && y >= 1 && y <= 3;
} void dfs(int now, int s) //从小到大预处理排列
{
if (now == 10) //搜完了
{
id[++cnt] = s; //存储排列
return; //记得返回
}
for (int i = 0; i <= 8; i+=1) //从小到大维护有序性
{
if (!usd[i]) //没有记录过
{
usd[i] = 1; //标记
dfs(now + 1, s * 10 + i); //搜索下一层
usd[i] = 0; //回溯
}
}
} inline int erfen(int x) //二分x在所有排列中的下标
{
int l = 1, r = cnt, ans = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (id[mid] == x) {ans = mid; break;}
else if (id[mid] < x) l = mid + 1;
else r = mid - 1;
}
return ans;
} int s, t; inline int bfs() //双向广搜
{
q.push(erfen(s)), f[erfen(s)] = 1;
q.push(erfen(t)), f[erfen(t)] = -1;
//记得存储的是下标
while (!q.empty()) //队列不为空
{
int u = q.front(); q.pop(); //弹出队头
getditu(id[u]); //转换为地图
for (int i = 0; i < 4; i+=1) //枚举上下左右四个方向
{
if (check(xx + dx[i], yy + dy[i]))
{
swap(mp[xx][yy], mp[xx + dx[i]][yy + dy[i]]); //交换
int tmp = erfen(getid()); //要转移的状态
if (!f[tmp]) //没有访问过
{
f[tmp] = f[u] + f[u] / abs(f[u]); //记录
q.push(tmp); //装进队列
}
else if (f[tmp] * f[u] < 0) //找到答案了
{
return abs(f[tmp] - f[u]) - 1; //返回答案
}
swap(mp[xx][yy], mp[xx + dx[i]][yy + dy[i]]); //记得换回
}
}
}
return -1;
} int main()
{
//freopen(".in", "r", stdin);
//freopen(".out", "w", stdout);
s = gi(), t = 123804765; //s为起点,t为终点
if (s == t) {puts("0"); return 0;} //特判起点终点相同的情况
dfs(1, 0); //预处理出排列
printf("%d\n", bfs()); //输出答案
return 0;
}

其实这题还有一种使用\(\text{IDA*}\)的做法。

由于博主太懒,所以直接放代码啦。

我把估价函数设为当前地图与目标状态地图有几处不同。

#include <bits/stdc++.h>
#define itn int
#define gI gi using namespace std; inline int gi()
{
int f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return f * x;
} int x, n, d, pp[5][5], ap[5][5], pd, xx, yy;
const int dx[] = {1, 0, 0, -1}, dy[] = {0, 1, -1, 0}; inline bool check()
{
for (int i = 1; i <= 3; i+=1) for (int j = 1; j <= 3; j+=1)
{
if (pp[i][j] != ap[i][j]) return false;
}
return true;
} inline bool chk(int x, int y) {return x >= 1 && x <= 3 && y >= 1 && y <= 3;} inline bool test_A_xing(int now) //估价函数
{
int cnt = 0;
for (int i = 1; i <= 3; i+=1) for (int j = 1; j <= 3; j+=1)
{
if (ap[i][j] != pp[i][j]) if ((++cnt) + now > d) return false;
}
return true;
} void A_xing(int now, int x, int y, int lst)
{
if (now == d) {if (check()) pd = 1; return;}
if (pd) return;
for (int i = 0; i < 4; i+=1)
{
int nx = x + dx[i], ny = y + dy[i];
if (nx < 1 || nx > 3 || ny < 1 || ny > 3 || lst + i == 3/*避免走重复的路*/) continue;
swap(ap[x][y], ap[nx][ny]);
if (test_A_xing(now) && !pd) A_xing(now + 1, nx, ny, i);
swap(ap[x][y], ap[nx][ny]);
}
} int main()
{
//freopen(".in", "r", stdin);
//freopen(".out", "w", stdout);
x = gi();
for (int i = 0; i < 9; i+=1)
{
int y = x % 10;
x /= 10;
ap[i / 3 + 1][i % 3 + 1] = y;
if (y == 0) xx = i / 3 + 1, yy = i % 3 + 1;
}
int t = 123804765;
for (int i = 0; i < 9; i+=1)
{
int y = t % 10;
pp[i / 3 + 1][i % 3 + 1] = y;
t /= 10;
}
if (check()) {puts("0"); return 0;}
for (d = 1; ; d+=1) //迭代加深
{
A_xing(0, xx, yy, -1); //A*搜索
if (pd) {printf("%d\n", d); break;} //搜到了结果
}
return 0;
}

题解【洛谷P1379】八数码难题的更多相关文章

  1. 洛谷 P1379 八数码难题 解题报告

    P1379 八数码难题 题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出一种初始布局(初 ...

  2. 洛谷——P1379 八数码难题

    P1379 八数码难题 双向BFS 原来双向BFS是这样的:终止状态与起始状态同时入队,进行搜索,只不过状态标记不一样而已,本题状态使用map来存储 #include<iostream> ...

  3. 洛谷P1379八数码难题

    题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中. 要求解的问题是:给出一种初始布局(初始状态)和目标布局(为 ...

  4. 洛谷 P1379 八数码难题 Label:判重&&bfs

    特别声明:紫书上抄来的代码,详见P198 题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给 ...

  5. 洛谷 P1379 八数码难题 题解

    我个人感觉就是一道bfs的变形,还是对bfs掌握不好的人有一定难度. 本题思路: 大体上用bfs搜,用map来去重,在这里只需要一个队列,因为需要较少步数达到的状态一定在步数较多的状态之前入队列. # ...

  6. 洛谷 P1379 八数码难题

    题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了 ...

  7. 洛谷 - P1379 - 八数码难题 - bfs

    https://www.luogu.org/problemnew/show/P1379 #include <bits/stdc++.h> using namespace std; #def ...

  8. 洛谷—— P1379 八数码难题

    https://daniu.luogu.org/problem/show?pid=1379 题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示 ...

  9. 洛谷P1379 八数码难题

    传送门 1.先用dfs枚举9!的全排列,存到hash数组里(类似离散化),因为顺序枚举,就不需要排序了 2.朴素bfs,判重就用二分找hash:如果发现当前状态=要求状态,输出步数结束程序 上代码 # ...

  10. 洛谷 P1379 八数码难题(map && 双向bfs)

    题目传送门 解题思路: 一道bfs,本题最难的一点就是如何储存已经被访问过的状态,如果直接开一个bool数组,空间肯定会炸,所以我们要用另一个数据结构存,STL大法好,用map来存,直接AC. AC代 ...

随机推荐

  1. pgspider gzip fdw试用(集成gzip+http+graphql-engine)

    gzip 也是一个在实际中比较有用的处理工具,可以减少数据传输,以下是集成gzip http 以及plv8 的处理 gzip Docker 镜像 Dockerfile FROM dalongrong/ ...

  2. 走进MEasy的世界:基于STM32MP1的IOT参考设计

    前言:在万物互联快速发展的趋势下,板卡处理器性能.内存大小.接口外设等都是人们非常关心的硬件参数,但是如何让硬件的作用实现它的功能最大化,一套完善的软件支持尤为重要. 背景:随着HTML5技术的发展, ...

  3. Java学习随笔---常用API(二)

    Object类的toString方法 将一个对象返回为字符串形式,但一般使用的时候会覆盖重写toString方法 Object类是所有类的父亲 // public class Person { pri ...

  4. 吴裕雄--天生自然HADOOP操作实验学习笔记:hdfs简单的shell命令

    实验目的 了解bin/hadoop脚本的原理 学会使用fs shell脚本进行基本操作 学习使用hadoop shell进行简单的统计计算 实验原理 1.hadoop的shell脚本 当hadoop集 ...

  5. BSP与HAL关系(转)

    板级支持包(BSP)(Board Support Package)是介于主板硬件和操作系统中驱动层程序之间的一层,一般认为它属于操作系统一部分,主要是实现对操作系统的支持,为上层的驱动程序提供访问硬件 ...

  6. dubbox的小案例

    什么是Dubbox: Dubbo是一个被国内很多互联网公司广泛使用的开源分布式服务框架,即使从国际视野来看应该也是一个非常全面的SOA基础框架.作为一个重要的技术研究课题,在当当网根据自身的需求,为D ...

  7. pyqt5-进度条控制

    1.基于自定义类的方式 继承自QProgressBar类,然后重写timerEvent方法,当该组件设置定时器的时候,会自己处理定时的处理方法,完成相应的功能 from PyQt5.Qt import ...

  8. win10家庭版更改本地账户名、C盘Users下文件夹名和环境变量等

    PS:由于四五年前装系统的时候懵懵懂懂的敲了一个中文用户名(有一个字还打错了,尴尬),导致现在打开cmd默认是C:\Users\中文名,path环境变量中也有中文路径,有时候有些程序的路径也是中文,这 ...

  9. gulp常用插件之pump使用

    更多gulp常用插件使用请访问:gulp常用插件汇总 pump这是一款小型节点模块,可将流连接在一起并在其中一个关闭时将其全部销毁. 使用标准source.pipe(dest)源时,如果dest发出关 ...

  10. 在RYU中实现交换机的功能

    首先源码,解析部分如下,同时可以参考RYU_BOOK上的解释说明  原文链接参考:https://blog.csdn.net/qq_34099967/article/details/89047741 ...