原题链接

题目描述

设有n个大小不等的中空圆盘,按从小到大的顺序从1到n编号。将这n个圆盘任意的迭套在三根立柱上,立柱的编号分别为A、B、C,这个状态称为初始状态。

现在要求找到一种步数最少的移动方案,使得从初始状态转变为目标状态。

移动时有如下要求:

·一次只能移一个盘;

·不允许把大盘移到小盘上面。

输入输出格式

输入格式:

文件第一行是状态中圆盘总数;

第二到第四行分别是初始状态中A、B、C柱上圆盘的个数和从上到下每个圆盘的编号;

第五到第七行分别是目标状态中A、B、C柱上圆盘的个数和从上到下每个圆盘的编号。

输出格式:

每行一步移动方案,格式为:move I from P to Q

最后一行输出最少的步数。

输入输出样例

输入样例#1:

5
3 3 2 1
2 5 4
0
1 2
3 5 4 3
1 1
输出样例#1:

move 1 from A to B
move 2 from A to C
move 1 from B to C
move 3 from A to B
move 1 from C to B
move 2 from C to A
move 1 from B to C
7

说明

圆盘总数≤45

每行的圆盘描述是从下到上的圆盘编号

题解

这道题目是经典的汉诺塔问题,没什么技术,但思维难度较高,如果条件判断太多则编码难度也会较高

首先,我们很容易想到一种假算法:(一定要注意它是错的,但对真算法有启发意义)

因为大盘子无法在小盘子上移动,而大盘子移动好之后又不会影响小盘子(这是本题所有操作的前提),故可以从大盘子开始移动。

我们从第N号盘子开始操作,计当前盘子为i号,如果它在原位置,那么就跳过,否则就将1~i-1号盘子都移动到不会动用的盘子,将目标盘子空出,然后将i号盘子放进去。经过N次操作,一定可以完成,但不能保证最优。

如图所示,如果我们要将第1号桩上的1个大盘子移到2号桩,那么我们就先将1、2号桩上所有比i号盘子小的盘子都移到第3号桩

实现这一过程很简单,只要每次将1~i-1号盘子移动到闲置的位置,然后移动即可,代码也十分简单

 #include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF=1e9+,MAXN=;
int N,cur[MAXN],goal[MAXN],ans;
char tran[]={' ','A','B','C'};/*translate*/
inline void input(int *array,int opt){
int ii,jj;
scanf("%d",&ii);
while(ii--){
scanf("%d",&jj);
array[jj]=opt;
}
}
void dfs(int from/*from idx*/,int to/*to pos*/){/*move the "from"-th plate to position "to"*/
if(cur[from]==to)
return;
int other=-cur[from]-to;
for(int i=from-;i>=;i--)
dfs(i,other);
printf("move %d from %c to %c\n",from,tran[cur[from]],tran[to]);
cur[from]=to;
ans++;
}
int main(){
scanf("%d",&N);
input(cur,);
input(cur,);
input(cur,);
input(goal,);
input(goal,);
input(goal,); for(int i=N;i>=;i--)
dfs(i,goal[i]);
printf("%d",ans);
return ;
}

但正如上面所说的,这是一个假算法。我们认定如图的变换方法是正确的,是建立在汉诺塔的性质的基础上的。在汉诺塔中,未归位的盘子都是连续叠加的(如下图1),不可能出现如图2的情况

这样的话,无论将这个连续的塔从哪里移动到哪里都是等价的,故开始给出的假算法是成立的。

然而可惜的是,我们的塔最初是随机摆放的,所以会有另一种方法

可以证明,没有其他移动方法了。但是否每次都要用两种方法呢?显然不是。观察两个图组可以发现,在进行了一次操作后,就还原到了汉诺塔的基本结构:一串连续的盘子,就可以用常规的方法操作了。

那图组2和图组1的过程又怎么实现呢?我们需要三种操作

  1. move(idx,from,to):当1~idx在同一个桩子上时,将它们移到to号桩子
  2. merge(idx,to):当1~idx不在同一个桩子上时,将它们移到to号桩子
  3. solve(idx,to):当1~idx归位

move函数的实现很简单,递归将1~idx-1号盘子移到闲置的位置,将idx号盘子移到to,再将1~idx-1号盘子移到to即可

void mov(int idx,int from,int to,int other){
/*move 1~"idx"-th to "to"
in a heap*/
if(!idx)
return;
mov(idx-,from,other,to);
ans[st][++sz[st]]=(state){idx,from,to};
mov(idx-,other,to,from);
}

merge函数也不难,将递归将1~idx-1号盘子移到闲置的位置(用merge),将idx号盘子移到to,再将1~idx-1号盘子移到to即可(用move)

void merg(int idx,int to){
/*move 1~"idx"-th to "to"
not in a heap*/
if(!idx)
return;
if(cur[idx]==to)
return merg(idx-,to);
int other=-cur[idx]-to;
merg(idx-,other);
ans[st][++sz[st]]=(state){idx,cur[idx],to};
mov(idx-,other,to,cur[idx]);
}

solve函数的思想同上

void solve(int idx,int to){
/*put 1-"idx"-th into its place*/
if(!idx)
return;
if(goal[idx]==to)
return solve(idx-,to);
int other=-goal[idx]-to;
mov(idx-,to,other,goal[idx]);
ans[st][++sz[st]]=(state){idx,to,goal[idx]};
solve(idx-,other);
}

有了这些操作,解题就很简单了,我们按照上面讲的两种情况分类,输出步数较少的方案即可

void work(int idx){
if(!idx)
return;
if(cur[idx]==goal[idx])
return work(idx-);
int other=-cur[idx]-goal[idx];
/*0:all to other, N to to*/
st=;
merg(idx-,other);
ans[st][++sz[st]]=(state){idx,cur[idx],goal[idx]};
solve(idx-,other);
/*1:all to to, N to other, all to from, N to to*/
st=;
merg(idx-,goal[idx]);
ans[st][++sz[st]]=(state){idx,cur[idx],other};
mov(idx-,goal[idx],cur[idx],other);
ans[st][++sz[st]]=(state) {idx,other,goal[idx]};
solve(idx-,cur[idx]);
}

最终给出AC代码

 #include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF=1e9+,MAXN=,MAXP=1e5;
int N,cur[MAXN],goal[MAXN],cnt;
char tran[]={'E','A','B','C'};/*translate*/
inline void input(int *array,int opt){
int ii,jj;
scanf("%d",&ii);
while(ii--){
scanf("%d",&jj);
array[jj]=opt;
}
}
struct state{ int idx,from,to; }ans[][MAXP];
int st,sz[];
void mov(int idx,int from,int to,int other){
/*move 1~"idx"-th to "to"
in a heap*/
if(!idx)
return;
mov(idx-,from,other,to);
ans[st][++sz[st]]=(state){idx,from,to};
mov(idx-,other,to,from);
}
void merg(int idx,int to){
/*move 1~"idx"-th to "to"
not in a heap*/
if(!idx)
return;
if(cur[idx]==to)
return merg(idx-,to);
int other=-cur[idx]-to;
merg(idx-,other);
ans[st][++sz[st]]=(state){idx,cur[idx],to};
mov(idx-,other,to,cur[idx]);
}
void solve(int idx,int to){
/*put 1-"idx"-th into its place*/
if(!idx)
return;
if(goal[idx]==to)
return solve(idx-,to);
int other=-goal[idx]-to;
mov(idx-,to,other,goal[idx]);
ans[st][++sz[st]]=(state){idx,to,goal[idx]};
solve(idx-,other);
}
void work(int idx){
if(!idx)
return;
if(cur[idx]==goal[idx])
return work(idx-);
int other=-cur[idx]-goal[idx];
/*0:all to other, N to to*/
st=;
merg(idx-,other);
ans[st][++sz[st]]=(state){idx,cur[idx],goal[idx]};
solve(idx-,other);
/*1:all to to, N to other, all to from, N to to*/
st=;
merg(idx-,goal[idx]);
ans[st][++sz[st]]=(state){idx,cur[idx],other};
mov(idx-,goal[idx],cur[idx],other);
ans[st][++sz[st]]=(state) {idx,other,goal[idx]};
solve(idx-,cur[idx]);
}
int main(){
scanf("%d",&N);
input(cur,);
input(cur,);
input(cur,);
input(goal,);
input(goal,);
input(goal,); work(N); for(int i=,ed=min(sz[],sz[]),j=sz[]>sz[];i<=ed;i++)
printf("move %d from %c to %c\n",ans[j][i].idx,tran[ans[j][i].from],tran[ans[j][i].to]);
printf("%d",min(sz[],sz[]));
return ;
}

洛谷 P1242 新汉诺塔的更多相关文章

  1. 洛谷P1242 新汉诺塔(dfs,模拟退火)

    洛谷P1242 新汉诺塔 最开始的思路是贪心地将盘子从大到小依次从初始位置移动到目标位置. 方法和基本的汉诺塔问题的方法一样,对于盘子 \(i\) ,将盘子 \(1\to i-1\) 放置到中间柱子上 ...

  2. 洛谷P1242 新汉诺塔

    传送门啦 首先要将第n个盘子从x到y,那么就要把比n小的盘子全部移到6-x-y,然后将n移到y 仔细想想:6代表的是3根初始柱,3根目标柱. 6-(x+y) 便是我们的中转柱了,因为到这个位置是最优的 ...

  3. 洛谷P1242 新汉诺塔 【神奇的递归】

    题目描述 设有n个大小不等的中空圆盘,按从小到大的顺序从1到n编号.将这n个圆盘任意的迭套在三根立柱上,立柱的编号分别为A.B.C,这个状态称为初始状态. 现在要求找到一种步数最少的移动方案,使得从初 ...

  4. P1242 新汉诺塔(搜索+模拟退火)

    题目链接:传送门 题目大意: 汉诺塔,给定n个盘子(n <= 45),起始状态和结束状态,求最小的步数以及路径. 思路: 考虑用dfs贪心地将剩余最大盘归位. #include<bits/ ...

  5. BZOJ1019 汉诺塔/洛谷P4285 [SHOI2008]汉诺塔

    汉诺塔(BZOJ) P4285 [SHOI2008]汉诺塔 居然是省选题,还是DP!(我的DP菜得要死,碰见就丢分) 冥思苦想了1h+ \(\to\) ?! 就是普通的hanoi NOI or HNO ...

  6. P1242 新汉诺塔(hanio)

    这道题加深了hanio的理解 如果我们要移动第n个盘子.那么就是说,n+1以后(包括n+1)的盘子都已经到位了 #include<iostream> #include<cstdio& ...

  7. P1242 新汉诺塔

    题目描述 设有n个大小不等的中空圆盘,按从小到大的顺序从1到n编号.将这n个圆盘任意的迭套在三根立柱上,立柱的编号分别为A.B.C,这个状态称为初始状态. 现在要求找到一种步数最少的移动方案,使得从初 ...

  8. 大白_uva10795_新汉诺塔

    题意:给出所有盘子的初态和终态,问最少多少步能从初态走到终态,其余规则和老汉诺塔一样. 思路: 若要把当前最大的盘子m从1移动到3,那么首先必须把剩下的所有盘子1~m-1放到2上,然后把m放到3上. ...

  9. UVA 10795 新汉诺塔问题

    https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...

随机推荐

  1. (c# )操作Excel的时候出现:不能使用对象或链接

    可能就是你打开了多个Excel进程没有关掉出现的问题. 编程:对文件操作的时候要养成关掉进程的习惯 在c#中操作完后关闭资源的代入如下: System.Runtime.InteropServices. ...

  2. 大道浮屠诀---mysql5.7.28 for linux安装

    环境: redhat6.5 MySQL Community Server 5.7.28 https://dev.mysql.com/downloads/mysql/5.7.html 安装RMP包的具体 ...

  3. flink widow&window funcion&水印

    在定义了窗口分配器之后,我们需要为每一个窗口明确的指定计算逻辑,这个就是窗口函数要做的事情, 当系统决定一个窗口已经准备好执行之后,这个窗口函数将被用来处理窗口中的每一个元素(可能是 分组的). 谁可 ...

  4. java OOP第03章_继承、抽象类和抽象方法

    一. 为什么需要继承: 若多个类中都需要一些属性和方法,那么就可以将属性和方法抽取到一个父类中,需要的子类可以通过extends关键字去继承这个父类后拥有相应的属性和方法. 类.数组.方法----引用 ...

  5. Django 前后端数据传输、ajax、分页器

    返回ORM目录 Django ORM 内容目录: 一.MTV与MVC模式 二.多对多表三种创建方式 三.前后端传输数据 四.Ajax ​ 五.批量插入数据 六.自定义分页器 一.MTV与MVC模式 M ...

  6. 4、Docker网络访问

    现在我们已经可以熟练的使用docker命令操作镜像和容器,并学会了如何进入到容器中去,那么实际的工作中,我们通常是在Docker中部署服务,我们需要在外部通过IP和端口进行访问的,那么如何访问到Doc ...

  7. Python|读、写Excel文件(三种模块三种方式)

    python读写excel的方式有很多,不同的模块在读写的讲法上稍有区别: 用xlrd和xlwt进行excel读写: 用openpyxl进行excel读写: 用pandas进行excel读写: imp ...

  8. QT之发布

    https://blog.csdn.net/qq_40194498/article/details/79926054打开windows控制台直接输入 windeployqt --help 可以知道想要 ...

  9. ubuntu卸载node和npm

    sudo apt-get remove --purge npm sudo apt-get remove --purge nodejs sudo apt-get remove --purge nodej ...

  10. 关于SecureCRT不能显示输入、换行不正常

    网上绿色破解版的SecureCRT会碰到这种问题,即输入字符不显示在终端.换行后下一行的行首有很大一段退格: 如上,输入的指令不显示:回车后下一行的起始位置不对. 碰到这种问题,在波特率.奇偶校验.停 ...