洛谷 P1242 新汉诺塔
题目描述
设有n个大小不等的中空圆盘,按从小到大的顺序从1到n编号。将这n个圆盘任意的迭套在三根立柱上,立柱的编号分别为A、B、C,这个状态称为初始状态。
现在要求找到一种步数最少的移动方案,使得从初始状态转变为目标状态。
移动时有如下要求:
·一次只能移一个盘;
·不允许把大盘移到小盘上面。
输入输出格式
输入格式:
文件第一行是状态中圆盘总数;
第二到第四行分别是初始状态中A、B、C柱上圆盘的个数和从上到下每个圆盘的编号;
第五到第七行分别是目标状态中A、B、C柱上圆盘的个数和从上到下每个圆盘的编号。
输出格式:
每行一步移动方案,格式为:move I from P to Q
最后一行输出最少的步数。
输入输出样例
5
3 3 2 1
2 5 4
0
1 2
3 5 4 3
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的过程又怎么实现呢?我们需要三种操作
- move(idx,from,to):当1~idx在同一个桩子上时,将它们移到to号桩子
- merge(idx,to):当1~idx不在同一个桩子上时,将它们移到to号桩子
- 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 新汉诺塔的更多相关文章
- 洛谷P1242 新汉诺塔(dfs,模拟退火)
洛谷P1242 新汉诺塔 最开始的思路是贪心地将盘子从大到小依次从初始位置移动到目标位置. 方法和基本的汉诺塔问题的方法一样,对于盘子 \(i\) ,将盘子 \(1\to i-1\) 放置到中间柱子上 ...
- 洛谷P1242 新汉诺塔
传送门啦 首先要将第n个盘子从x到y,那么就要把比n小的盘子全部移到6-x-y,然后将n移到y 仔细想想:6代表的是3根初始柱,3根目标柱. 6-(x+y) 便是我们的中转柱了,因为到这个位置是最优的 ...
- 洛谷P1242 新汉诺塔 【神奇的递归】
题目描述 设有n个大小不等的中空圆盘,按从小到大的顺序从1到n编号.将这n个圆盘任意的迭套在三根立柱上,立柱的编号分别为A.B.C,这个状态称为初始状态. 现在要求找到一种步数最少的移动方案,使得从初 ...
- P1242 新汉诺塔(搜索+模拟退火)
题目链接:传送门 题目大意: 汉诺塔,给定n个盘子(n <= 45),起始状态和结束状态,求最小的步数以及路径. 思路: 考虑用dfs贪心地将剩余最大盘归位. #include<bits/ ...
- BZOJ1019 汉诺塔/洛谷P4285 [SHOI2008]汉诺塔
汉诺塔(BZOJ) P4285 [SHOI2008]汉诺塔 居然是省选题,还是DP!(我的DP菜得要死,碰见就丢分) 冥思苦想了1h+ \(\to\) ?! 就是普通的hanoi NOI or HNO ...
- P1242 新汉诺塔(hanio)
这道题加深了hanio的理解 如果我们要移动第n个盘子.那么就是说,n+1以后(包括n+1)的盘子都已经到位了 #include<iostream> #include<cstdio& ...
- P1242 新汉诺塔
题目描述 设有n个大小不等的中空圆盘,按从小到大的顺序从1到n编号.将这n个圆盘任意的迭套在三根立柱上,立柱的编号分别为A.B.C,这个状态称为初始状态. 现在要求找到一种步数最少的移动方案,使得从初 ...
- 大白_uva10795_新汉诺塔
题意:给出所有盘子的初态和终态,问最少多少步能从初态走到终态,其余规则和老汉诺塔一样. 思路: 若要把当前最大的盘子m从1移动到3,那么首先必须把剩下的所有盘子1~m-1放到2上,然后把m放到3上. ...
- UVA 10795 新汉诺塔问题
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...
随机推荐
- Android Telephony分析(四) ---- TelephonyManager详解
前言 TelephonyManager主要提供Telephony相关信息的查询/修改功能,以及Phone状态监听功能,封装的方法主要是提供给APP上层使用.TelephonyManager.java ...
- 根据单个或多个字段对list对象去重
pojo 省略 在list 对象中,根据某一字段进行去重,重写Comparator /** * 去重 * * @param orderList * @return * @author ziggo * ...
- 2- SQL语句的强化
查询类型cate_name为 '超极本' 的商品名称.价格 select name,price from goods where cate_name = '超级本'; 显示商品的种类 select c ...
- 利用msbuild白名单执行shellcode
x64:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe x32:C:\Windows\Microsoft.NET\Framewo ...
- TwainCapabilities
Twain Capabilities 2013年10月15日 ⁄ 综合 ⁄ 共 6098字 ⁄ 字号 小 中 大 ⁄ 评论关闭 转自:http://blog.163.com/lvan100@yeah/ ...
- Java怎样获取字符串最后出现的位置
lastIndexOf();表示获取字符串最后出现的位置,倒数的位置 @Test /** * lastIndexOf();//获取字符串最后出现的位置,倒数的位置 * */ public void f ...
- [JZOJ4616] 【NOI2016模拟7.12】二进制的世界
题目 题目大意 给你一个数列,每个数为[0,65535][0,65535][0,65535]内的整数. 给定一个位运算操作optoptopt,是andandand.ororor.xorxorxor中的 ...
- 【JZOJ2867】Contra
description 偶然间,chnlich 发现了他小时候玩过的一个游戏"魂斗罗",于是决定怀旧.但是这是一个奇怪的魂斗罗 MOD. 有 N 个关卡,初始有 Q 条命. 每通过 ...
- thinkphp 控制器定义
控制器和操作 一般来说,ThinkPHP的控制器是一个类,而操作则是控制器类的一个公共方法. 下面就是一个典型的控制器类的定义: <?php namespace Home\Controller; ...
- thinkphp 正则路由
正则路由也就是采用正则表达式定义路由的一种方式,依靠强大的正则表达式,能够定义更灵活的路由规则. 路由表达式支持的正则定义必须以“/”开头,否则就视为规则表达式.也就是说如果采用: '#^blog\/ ...