题目地址: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. springboot 报错 org.springframework.beans.factory.NoSuchBeanDefinitionException:No qualifying bean of type 'com.example.service.HrService' available: 有没有大佬出个主意,我找了一天,刚入门springboot

    话不多说先上图,这是启动类的配置,这里配置了@ComponentScan("我的mapper的接口") 接下来是我的项目结构截图 然后是service 的截图,我在这里加了注解@S ...

  2. Vue中进度条的使用

    1. 安装npm install --save nprogress 2.导入js和css import NProgress from 'nprogress'import 'nprogress/npro ...

  3. 阿里云搭建k8s高可用集群(1.17.3)

    首先准备5台centos7 ecs实例最低要求2c4G 开启SLB(私网) 这里我们采用堆叠拓扑的方式构建高可用集群,因为k8s 集群etcd采用了raft算法保证集群一致性,所以高可用必须保证至少3 ...

  4. Java 【instanceof使用】

    一.instanceof使用 public class demo{ public static void main(String[] args){ String name = “hello”; boo ...

  5. phpcms抛出的二维数组转移到js,js中for....in遍历数组,用“.”连接来读出一维数组值

    直切正题: 1.phpcms在模版中读出数组有很多中方法,如,{pc:content action="lists"}或{pc:get sql=""},经过{lo ...

  6. APFS 宗卷 • APFS(加密)磁盘格式怎么去掉?Mac磁盘加密怎么解除?

    相信很多朋友都因为APFS 宗卷 • APFS(加密)磁盘格式而困扰,这种磁盘加密,导致很多破解版软件都不能安装.那么磁盘加密怎么解除?小编翻阅了一些教程,为您带来APFS 宗卷 • APFS(加密) ...

  7. gulp常用插件之gulp-cache使用

    更多gulp常用插件使用请访问:gulp常用插件汇总 gulp-cache这是一款基于临时文件的gulp缓存代理任务. 更多使用文档请点击访问gulp-cache工具官网. 安装 一键安装不多解释 n ...

  8. the simmon effect(in psychology) :build the function of subject_information(modify the experiment programme),before we begin the experiment

    #the real experiment for simon effect #load the library which is our need import pygame import sys i ...

  9. IO流学习之字符流(三)

    IO流之字符流缓冲区: 概念: 流中的缓冲区:是先把程序需要操作的数据保存在内存中,然后我们的程序读写数据的时候,不直接和持久设备之间交互,而改成和内存中的数据进行交互. 缓冲区:它就是临时存储数据, ...

  10. Java多线程之synchronized和volatile

    概述 用Java来开发多线程程序变得越来越常见,虽然Java提供了并发包来简化多线程程序的编写,但是我们有必要深入研究一下,才能更好的掌握这块知识. 本文主要对Java提供的底层原语synchroni ...