原题链接

题目描述

设有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. 漏洞验证系列--MongoDB未授权访问

    本系列文章旨在对于有一定网络安全基础的人员,在日常工作中扫描出来的各种漏洞,如何进行验证,以区分该漏洞是否存在或是扫描器误报.请勿应用非法途径. 本漏洞是由于MongoDB未设置访问权限,用户可以直接 ...

  2. Spring AOP源码分析(二):AOP的三种配置方式与内部解析实现

    AOP配置 在应用代码中,可以通过在spring的XML配置文件applicationContext.xml或者基于注解方式来配置AOP.AOP配置的核心元素为:pointcut,advisor,as ...

  3. USACO2012 Haybale stacking /// 区间表示法 oj21556

    题目大意:N个方块 标号1~N  K个操作 操作a b 表示标号a~b区间每位多加一个方块 Input * Line 1: Two space-separated integers, N  K. * ...

  4. 【POJ】1321棋盘问题

    题目链接:http://poj.org/problem?id=1321 题意:见题干,很清楚了. 题解:简单dfs,参照八皇后 代码: #include<iostream> #includ ...

  5. Django ORM 之F、Q查询与事务

    返回ORM目录 Django ORM 内容目录 一.F.Q查询 二.事务 三.only与defer 一.F.Q查询 """ Product表中的数据: 1 橡皮 2 20 ...

  6. 2019-8-31-dotnet-数组自动转基类数组提示-Co-variant-array-conversion-是什么问题

    title author date CreateTime categories dotnet 数组自动转基类数组提示 Co-variant array conversion 是什么问题 lindexi ...

  7. Win和Linux定时备份数据库

    项目的数据库需要每天备份,但是手动备份太麻烦而且容易忘,所以通过定时任务执行脚本备份数据库,服务器有Windows和Linux,所以两种都记录一下. 一.Windows 首先写好脚本,这里不多说,因为 ...

  8. 360自动抢票还不够,几行js代码设置无人值守

    360就是牛逼哄哄的...... 但是最近在使用360浏览器抢票的时候还是发现了一些体验不好的地方,比如搞着搞着就退出了登录,有时候能帮你自动登录进去,但是自动登录之后又不会帮你自动开始抢.然后验证码 ...

  9. Font Awesome (Mark)

    Font Awesome为您提供可缩放的矢量图标,您可以使用CSS所提供的所有特性对它们进行更改,包括:大小.颜色.阴影或者其它任何支持的效果. 一个字库,675个图标 仅一个Font Awesome ...

  10. thinkphp cbd模式

    ThinkPHP从3.0版本开始引入了全新的CBD(核心Core+行为Behavior+驱动Driver)架构模式,因为从底层开始,框架就采用核心+行为+驱动的架构体系,核心保留了最关键的部分,并在重 ...