题目地址:https://www.luogu.com.cn/problem/P4289

题解原地址:https://createsj.blog.luogu.org/solution-p4289

让我们先来看看题目:

题目描述

在一个 \(4*4\) 的方框内摆放了若干个相同的玩具,某人想将这些玩具重新摆放成为他心中理想的状态,规定移动时只能将玩具向上下左右四个方向移动,并且移动的位置不能有玩具,请你用最少的移动次数将初始的玩具状态移动到某人心中的目标状态。

输入输出格式

输入格式:

前 \(4\) 行表示玩具的初始状态,每行 \(4\) 个数字 \(1\) 或 \(0\),\(1\) 表示方格中放置了玩具,\(0\) 表示没有放置玩具。接着是一个空行。接下来4行表示玩具的目标状态,每行 \(4\) 个数字 \(1\) 或 \(0\),意义同上。

输出格式:

一个整数,所需要的最少移动次数。

事实上,当看到 \(4\times4\) 和 \(1\) 或 \(0\) 时,就应该想到状压了,因为只有 \(4\times 4 =16\) 个方格,而每个方格只有 \(1\) 或 \(0\) 两种状态,所以总共只有 \(2^{16}=65536\) 种状态(由于玩具的个数是一定的,所以状态数还要小得多,不过状压到 \(65536\) 种已经够用了),一个 unsigned short 就可以储存下来了。为了方便起见,这里我为 unsigned short 取一个别名 ushort。

typedef unsigned short ushort;

接下来我们来写输入函数用于输入起始和目标状态:

inline bool getbool()//输入一个bool值(即 0 或 1)
{
char c;
do
c=getchar();
while(c!='1' && c!='0');
return c&1;
}
ushort input()
{
ushort num=0;
for(ushort i=0;i<16;++i)
num=(num<<1)|getbool();//num 左移一位后在后面插入一个bool值(0或1)
return num;
}

接下来就是解决移动玩具的问题了。

比如样例的初始状态为:

\[1\quad1\quad1\quad1//
0\quad0\quad0\quad0//
1\quad1\quad1\quad0//
0\quad0\quad1\quad0
\]

按照我给出的输入函数,初始状态被保存为二进制数

\[1111000011100010
\]

我们想要第 \(0\) 行第 \(0\) 列的玩具向下移动,即使初始状态变为:

\[0\quad1\quad1\quad1//
1\quad0\quad0\quad0//
1\quad1\quad1\quad0//
0\quad0\quad1\quad0
\]

保存为

\[0111100011100010
\]

很显然,这相当于交换两位数。

可以想到,我们可以先用 \(0\) 覆盖掉移动的位置,再将 \(1\) 插入,这一方法我们可以用位运算的方式实现。

先定义一个数组 \(f\):

const int f[4][4]= {{15,14,13,12},{11,10,9,8},{7,6,5,4},{3,2,1,0}};

这样会方便我们做位移之类的位运算操作。

另外利用该数组友情赠送 output 函数来输出状态(调试用):

void output(const ushort num)
{
for(int i=0;i<4;putchar('\n'),++i)
for(int j=0;j<4;++j)
putchar(((num>>f[i][j])&1)|'0');
}

用 \(now\) 来表示当前要移动的状态,\(x\) 和 \(y\) 表示移动的位置,bool 值 \(next\) 为真表示为上下移动,否则表示为左右移动。我们先用 \(t1\) 和 \(t2\) 储存移动前的位置和移动后的位置的状态(有无玩具):

const ushort t1=now&(1<<f[x][y]),t2=now&(1<<f[x+next][y+(!next)]);

然后覆盖的操作如下:

now=now&(~t1)&(~t2);

然后插入操作如下:

now=now|(t1>>f[x][y]<<f[x+next][y+(!next)])|(t2>>f[x+next][y+(!next)]<<f[x][y]);

两个操作合起来就是:

(now&(~t1)&(~t2))|(t1>>f[x][y]<<f[x+next][y+(!next)])|(t2>>f[x+next][y+(!next)]<<f[x][y]);

我们只需要用一个 \(\operatorname{move}\) 函数返回它的值即可:

inline ushort move(const ushort now,const ushort x,const ushort y,const bool next)
{
const ushort t1=now&(1<<f[x][y]),t2=now&(1<<f[x+next][y+(!next)]);
return (now&(~t1)&(~t2))|(t1>>f[x][y]<<f[x+next][y+(!next)])|(t2>>f[x+next][y+(!next)]<<f[x][y]);
}

解决了状态的储存和处理后,就来思考如何求出最少移动次数吧!

不难想到,我们可以用广搜来找出这个最少移动次数。首先从初始状态开始找交换一次后的所有状态,再继续找,直到出现目标状态,再输出次数,另外还要筛掉已经出现过的状态。

用一个结构体数组来储存一种状态是否出现过且出现在第几次:

struct st
{
ushort step;//储存出现次数
bool book;//储存是否出现过
}a[65536];

再来一些变量:

queue<ushort> q;//队列,广搜必需,储存状态
ushort end;//储存目标状态

自己写个 \(\operatorname{Push}\) 函数,其实就是在入队的时候做些特殊处理:

inline bool Push(const ushort x,const ushort y,const bool next)//参数与 move 函数的后三个参数相同
{
ushort t=move(q.front(),x,y,next);//t 储存移动后状态
if(t==end)//如果移动后为目标状态
{
printf("%d",a[q.front()].step+1);//输出移动步数
return true;//返回真,表示已经搜索到了目标状态
}
if(a[t].book)//如果该状态没有被标记过(即没有搜索到过)
return false;//返回假,表示没有搜索到目标状态
q.push(t);//入队
a[t].step=a[q.front()].step+1;//移动次数比原来多1
a[t].book=true;//给该状态打上标记
return false;//返回假,表示没有搜索到目标状态
}

\(\operatorname{bfs}\) (广搜)函数如下:

inline void bfs()
{
q.push(input());//输入初始状态并入队
a[q.front()].book=true;//打上标记
end=input();//输入目标状态
if(q.front()==end)//判断初始状态与目标状态是否相同,有一个测试点就是这样
{
putchar('0');//毋庸置疑,移动次数肯定为0
return;
}
//广搜代码(应该不需要多讲了吧?)
do{
for(ushort i=0; i<4; ++i)
for(ushort j=0; j<3; ++j)
{
if(Push(i,j,false))
return;
else if(Push(j,i,true))
return;
}
q.pop();
}while(!q.empty());
}

这样,整个问题就解决了!完整代码如下:

#include <queue>
#include <cstdio>
using namespace std;
typedef unsigned short ushort;
const int f[4][4]={{15,14,13,12},{11,10,9,8},{7,6,5,4},{3,2,1,0}};
inline bool getbool()//输入一个bool值(即 0 或 1)
{
char c;
do
c=getchar();
while(c!='1' && c!='0');
return c&1;
}
ushort input()
{
ushort num=0;
for(ushort i=0;i<16;++i)
num=(num<<1)|getbool();//num 左移一位后在后面插入一个bool值(0或1)
return num;
}
inline ushort move(const ushort now,const ushort x,const ushort y,const bool next)
{
const ushort t1=now&(1<<f[x][y]),t2=now&(1<<f[x+next][y+(!next)]);
return (now&(~t1)&(~t2))|(t1>>f[x][y]<<f[x+next][y+(!next)])|(t2>>f[x+next][y+(!next)]<<f[x][y]);
}
struct st
{
ushort step;//储存出现次数
bool book;//储存是否出现过
}a[65536];
queue<ushort> q;//队列,广搜必需,储存状态
ushort end;//储存目标状态
inline bool Push(const ushort x,const ushort y,const bool next)//参数与 move 函数的后三个参数相同
{
ushort t=move(q.front(),x,y,next);//t 储存移动后状态
if(t==end)//如果移动后为目标状态
{
printf("%d",a[q.front()].step+1);//输出移动步数
return true;//返回真,表示已经搜索到了目标状态
}
if(a[t].book)//如果该状态没有被标记过(即没有搜索到过)
return false;//返回假,表示没有搜索到目标状态
q.push(t);//入队
a[t].step=a[q.front()].step+1;//移动次数比原来多1
a[t].book=true;//给该状态打上标记
return false;//返回假,表示没有搜索到目标状态
}
inline void bfs()
{
q.push(input());//输入初始状态并入队
a[q.front()].book=true;//打上标记
end=input();//输入目标状态
if(q.front()==end)//判断初始状态与目标状态是否相同,有一个测试点就是这样
{
putchar('0');//毋庸置疑,移动次数肯定为0
return;
}
//广搜代码(应该不需要多讲了吧?)
do{
for(ushort i=0; i<4; ++i)
for(ushort j=0; j<3; ++j)
{
if(Push(i,j,false))
return;
else if(Push(j,i,true))
return;
}
q.pop();
}while(!q.empty());
}
int main()
{
bfs();
return 0;
}

还有一道类似的题目,A掉此题后也可以去做一做哦:

P1225 黑白棋游戏

题解 P4289 【[HAOI2008]移动玩具】的更多相关文章

  1. P4289 [HAOI2008]移动玩具(bfs)

    P4289 [HAOI2008]移动玩具 双向bfs+状态压缩+记忆化搜索 双向bfs用于对bfs的优化,每次找到可扩展节点少的一边进行一次bfs,找到的第一个互相接触的点即为最短路径 矩阵范围仅4* ...

  2. luogu P4289 [HAOI2008]移动玩具

    传送门 这道题可以二进制记录状态搜索 也可以做以下考虑 若一个棋子要移动到另一个位置上去,则步数为两点的曼哈顿距离(横坐标差的绝对值+纵坐标差的绝对值),因为假设路径上有其他的棋子,可以通过移动其他棋 ...

  3. P4289 [HAOI2008]移动玩具

    传送门 广搜 4*4 的方阵只有 0 和 1 显然可以状态压缩 (如样例的开始状态压缩后就是1111000011100010) 为了加快速度用了双向广搜(顺便学了一下双向广搜) 双向广搜顾名思义 就是 ...

  4. P4289 【一本通提高篇广搜的优化技巧】[HAOI2008]移动玩具

    [HAOI2008]移动玩具 题目描述 在一个 4 × 4 4\times4 4×4 的方框内摆放了若干个相同的玩具,某人想将这些玩具重新摆放成为他心中理想的状态,规定移动时只能将玩具向上下左右四个方 ...

  5. BZOJ 1054 [HAOI2008]移动玩具

    1054: [HAOI2008]移动玩具 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1388  Solved: 764[Submit][Statu ...

  6. 1054: [HAOI2008]移动玩具

    1054: [HAOI2008]移动玩具 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1272  Solved: 690[Submit][Statu ...

  7. 【BZOJ1054】[HAOI2008]移动玩具

    [BZOJ1054][HAOI2008]移动玩具 题面 bzoj 洛谷 题解 太\(sb\)了,不想写了,直接点开洛谷题面单击右边蓝色按钮题解即可

  8. 【BZOJ1054】[HAOI2008]移动玩具 BFS

    [BZOJ1054][HAOI2008]移动玩具 Description 在一个4*4的方框内摆放了若干个相同的玩具,某人想将这些玩具重新摆放成为他心中理想的状态,规定移动 时只能将玩具向上下左右四个 ...

  9. bzoj 1054: [HAOI2008]移动玩具 bfs

    1054: [HAOI2008]移动玩具 Time Limit: 10 Sec  Memory Limit: 162 MB[Submit][Status][Discuss] Description 在 ...

  10. bzoj1054: [HAOI2008]移动玩具

    hash+bfs:要注意特殊情况.(似乎连sort.lower_bound都不用数据小直接判重了... #include<cstdio> #include<cstring> # ...

随机推荐

  1. 大数据才是未来,Oracle、SQL Server成昨日黄花?

    1. 引子**** 有人在某个专注SQL的公众号留言如下: 这个留言触碰到一个非常敏感的问题:搞关系型数据库还有前途吗?现在都2020年了,区块链正火热,AI人才已经"过剩",大数 ...

  2. Case Study - 预测肺癌

    Problem 肺癌是发病率和死亡率增长最快,对人群健康和生命威胁最大的恶性肿瘤之一.近50年来许多国家都报道肺癌的发病率和死亡率均明显增高,男性肺癌发病率和死亡率均占所有恶性肿瘤的第一位,女性发病率 ...

  3. ftp 拉去远程文件脚本

    ftp 拉去远程文件脚本 cat ftp.sh #!/bin/bash ftp -i -n 192.168.1.1 << EOF user ftpadmin gaofeng binary ...

  4. 吴裕雄--天生自然HADOOP操作实验学习笔记:分布式及RPC通信简介

    实验目的 掌握GOF设计模式的代理模式 了解掌握socket编程.java反射.动态代理 了解NIO.多线程 掌握hadoop的RPC框架使用API 实验原理 1.什么是RPC 在hadoop出现以前 ...

  5. 小程序公共模板template与公共js数据utils的引用实例

    在小程序项目开发中,经常会遇到公共模板与公共js数据的调用,这里结合自己的项目为这一需求做一简单介绍 目录截图 现在是有一个评论版块需要在几个页面里共用 先将评论版块的wxml剔出来放在templat ...

  6. 面试官:说说TCP和UDP的区别和应用场景

    原创文章首发于公众号:「码农富哥」,欢迎收藏和关注,如转载请注明出处! 上一篇聊完 一文彻底搞懂 TCP三次握手.四次挥手过程及原理 这次聊聊TCP和UDP的区别和场景 TCP/IP 中有两个具有代表 ...

  7. SpringBoot2.x-笔记(01)

    程序入口 @SpringBootApplication public class SpringbootApplication { public static void main(String[] ar ...

  8. sql注入文件写入和读取

    系统固定文件路径:https://blog.csdn.net/ncafei/article/details/54616826 /etc/passwd c:/windows/win.ini 文件读取使用 ...

  9. PAT (Advanced Level) Practice 1028 List Sorting (25 分) (自定义排序)

    Excel can sort records according to any column. Now you are supposed to imitate this function. Input ...

  10. vue自学入门-7(vue style scope)

    vue自学入门-1(Windows下搭建vue环境) vue自学入门-2(vue创建项目) vue自学入门-3(vue第一个例子) vue自学入门-4(vue slot) vue自学入门-5(vuex ...