汉诺塔系列问题

HDU-1207 汉诺塔II

题目链接

https://vjudge.net/problem/HDU-1207

题面

Description

经典的汉诺塔问题经常作为一个递归的经典例题存在。可能有人并不知道汉诺塔问题的典故。汉诺塔来源于印度传说的一个故事,上帝创造世界时作了三根金刚石柱子,在一根柱子上从下往上按大小顺序摞着64片黄金圆盘。上帝命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一回只能移动一个圆盘。有预言说,这件事完成时宇宙会在一瞬间闪电式毁灭。也有人相信婆罗门至今仍在一刻不停地搬动着圆盘。恩,当然这个传说并不可信,如今汉诺塔更多的是作为一个玩具存在。Gardon就收到了一个汉诺塔玩具作为生日礼物。
  Gardon是个怕麻烦的人(恩,就是爱偷懒的人),很显然将64个圆盘逐一搬动直到所有的盘子都到达第三个柱子上很困难,所以Gardon决定作个小弊,他又找来了一根一模一样的柱子,通过这个柱子来更快的把所有的盘子移到第三个柱子上。下面的问题就是:当Gardon在一次游戏中使用了N个盘子时,他需要多少次移动才能把他们都移到第三个柱子上?很显然,在没有第四个柱子时,问题的解是2^N-1,但现在有了这个柱子的帮助,又该是多少呢?

Input

包含多组数据,每个数据一行,是盘子的数目N(1<=N<=64)。

Output

对于每组数据,输出一个数,到达目标需要的最少的移动数。

Sample Input

1
3
12

Sample Output

1
5
81

题解

我们设\(g[i]\)为借助2个柱子移动i个盘子到某个柱子上所花费的步数,\(f[i]\)为借助1个柱子移动i个盘子到某个柱子所花费的步数,那么我们要移动n个盘子到第四根柱上,首先要把k个盘子借助中间两个柱子移动到第4个柱子上,然后把n-k个柱子借助中间一个柱子移动到第4个柱子上,然后把k个盘子借助剩下的两个柱子移动到第4个柱子上,也就是\(g[n]=min(g[n],2\times g[k])+f[n-k](k=1...n-1)\),对于\(f[i]\)我们知道为\(2^i-1\),递推处理出\(g[i]\)就好了。

AC代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 70
using namespace std;
long long f[N];
long long g[N];
int main() {
	f[0] = 0;
	for (int i = 1; i <= 63; i++) {
		f[i] = 2 * (f[i - 1] + 1) - 1;
	}
	long long inf = 0x7fffffffffffff;
	fill(g + 1, g + 66, inf);
	g[1] = 1;
	g[2] = 3;
	for (int i = 3; i <= 64; i++) {
		for (int k = 1; k < i; k++) {
			if (i == 64 && k == 1) continue;
			g[i] = min(g[i], 2 * g[k] + f[i - k]);
		}
	}
	int n;
	while (scanf("%d", &n) != EOF) {
		printf("%lld\n", g[n]);
	}
	return 0;
}

汉诺塔III

题目链接

https://vjudge.net/problem/HDU-2064

题面

Description

约19世纪末,在欧州的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下、由小到大顺序串着由64个圆盘构成的塔。目的是将最左边杆上的盘全部移到右边的杆上,条件是一次只能移动一个盘,且不允许大盘放在小盘的上面。
现在我们改变游戏的玩法,不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出),也不允许大盘放到下盘的上面。
Daisy已经做过原来的汉诺塔问题和汉诺塔II,但碰到这个问题时,她想了很久都不能解决,现在请你帮助她。现在有N个圆盘,她至少多少次移动才能把这些圆盘从最左边移到最右边?

Input

包含多组数据,每次输入一个N值(1<=N=35)。

Output

对于每组数据,输出移动最小的次数。

Sample Input

1
3
12

Sample Output

2
26
531440

题解

设移动k个圆盘从第一根柱子到第三根柱子需要\(f[k]\)次移动,则移动K-1个圆盘道第三根柱子需要\(f[k-1]\)次移动,再将最大的圆盘移动到中间柱子需要1次移动,然后将k-1个圆盘移动回第一根柱子同样需要\(f[k-1 ]\)次移动,移动最大的盘子到第三根柱子需要1次移动,最后将k-1个圆盘也移动到第三根柱子需要\(f[k-1]\)次移动,这样递归公式就是\(f[k]=3*f[k-1]+2\)。而递归的出口是k=1时,f[1]=2

AC代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 50
using namespace std;
long long f[N];
int main() {
	int n;
	f[1] = 2;
	for (int i = 2; i <= 35; i++) {
		f[i] = f[i - 1] * 3 + 2;
	}
	while (scanf("%d", &n) != EOF) {
		printf("%lld\n", f[n]);
	}
	return 0;
}

HDU-1995 汉诺塔V

题目链接

https://vjudge.net/problem/HDU-1995

题面

Description

用1,2,...,n表示n个盘子,称为1号盘,2号盘,...。号数大盘子就大。经典的汉诺塔问
题经常作为一个递归的经典例题存在。可能有人并不知道汉诺塔问题的典故。汉诺塔来源于
印度传说的一个故事,上帝创造世界时作了三根金刚石柱子,在一根柱子上从下往上按大小
顺序摞着64片黄金圆盘。上帝命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱
子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一回只能移动一个圆盘。我们
知道最少需要移动2^64-1次.在移动过程中发现,有的圆盘移动次数多,有的少 。 告之盘
子总数和盘号,计算该盘子的移动次数.

Input

包含多组数据,首先输入T,表示有T组数据.每个数据一行,是盘子的数目N(1<=N<=60)和盘
号k(1<=k<=N)。

Output

对于每组数据,输出一个数,到达目标时k号盘需要的最少移动数。

Sample Input

2
60 1
3 1

Sample Output

576460752303423488
4

题解

由于比k小的盘子移动不会牵扯到k盘子的移动,所以问题转换为n-k+1阶汉诺塔第一个盘子的移动次数

对于n阶的汉诺塔,移动分为3步

  1. 将A上的n-1个盘子借助C移动到B
  2. 将A上的1个盘子移动到C
  3. 将n-1个盘子借助A移动到C

在第二步第一个盘子未动,所以第一个盘子的移动步数递推公式为\(f[i]=2*f[i-1]\),所以n阶汉诺塔第k个盘子的移动步数就为\(2^{n-k}\)

AC代码

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 55
using namespace std;
long long f[N];
long long fpow(long long a, int b) {
	long long ans = 1;
	while (b) {
		if (b & 1) ans *= a;
		a *= a;
		b >>= 1;
	}
	return ans;
}
int main() {
	int t;
	scanf("%d", &t);
	while (t--) {
		int n, k;
		scanf("%d%d", &n, &k);
		printf("%lld\n", fpow(2, n - k));
	}
	return 0;
}

汉诺塔VIII(较难)

题目链接

https://vjudge.net/problem/HDU-2184

题面

Description

1,2,...,n表示n个盘子.数字大盘子就大.n个盘子放在第1根柱子上.大盘不能放在小盘上.在第1根柱子上的盘子是a[1],a[2],...,a[n]. a[1]=n,a[2]=n-1,...,a[n]=1.即a[1]是最下面的盘子.把n个盘子移动到第3根柱子.每次只能移动1个盘子,且大盘不能放在小盘上.问移动m次后的状况.

Input

第1行是整数T,表示有T组数据,下面有T行
每行2个整数n (1 ≤ n ≤ 63) ,m≤ 2^n-1

Output

输出移动m次后的状况.每组输出3行,第i行第1个数是第i根柱子上的盘子数,然后是盘子的号数.

Sample Input

3
3 2
4 5
39 183251937942

Sample Output

1 3
1 2
1 1
2 4 1
1 3
1 2
13 39 36 33 30 27 24 21 18 15 12 9 4 1
12 38 35 32 29 26 23 20 17 14 11 8 5
14 37 34 31 28 25 22 19 16 13 10 7 6 3 2

题解

我们知道移动n阶汉诺塔需要\(2^n-1\)步,则移动n-1个盘子到b盘需要\(2^{n-1}-1\)步,移动最大的盘子到c盘需要1步,移动n-1个盘子到c盘需要\(2^{n-1}-1\),然后我们可以递归处理移动m次盘子时,每个盘子的状况

首先从第n个盘子,移动m次开始处理,有以下三种情况

  1. 如果\(m>2^{n-1}\),那么第n个盘子肯定在c柱上,我们就先把第n个盘子放到c柱上,花费\(2^{n-1}\)步,剩下的n-1个盘子剩下\(m-2^{n-1}\)步,借助a柱移动到c柱上

  2. 如果\(m=2^{n-1}\)那么正好是第n个盘子在c柱上,其他均在b柱上,直接记录进数组,不再递归

  3. 如果\(m<2^{n-1}\),说明第n个盘子只能在a柱上,用m步移动剩下的n-1个盘子,借助c柱移动到b柱

递归结束后,每个柱子上有哪些盘子就被确定了

AC代码

#include<bits/stdc++.h>
#define N 70
using namespace std;
typedef unsigned long long ll;
ll a[N], b[N], c[N];
ll pow2[N];
void dfs(int n, ll m, ll a[], ll b[], ll c[]) {
	if (m > pow2[n - 1]) {
		c[++c[0]] = n;
		dfs(n - 1, m - pow2[n - 1], b, a, c);
	}
	else if (m == pow2[n - 1]) {
		c[++c[0]] = n;
		for (int i = n - 1; i >= 1; i--) {
			b[++b[0]] = i;
		}
	}
	else {
		a[++a[0]] = n;
		dfs(n - 1, m, a, c, b);
	}
}
int main() {
	pow2[0] = 1;
	for (int i = 1; i <= 63; i++) {
		pow2[i] = pow2[i - 1] * 2;
	}
	int t;
	scanf("%d", &t);
	while (t--) {
		a[0] = b[0] = c[0] = 0;
		ll n, m;
		scanf("%llu%llu", &n, &m);
		dfs(n, m, a, b, c);
		printf("%llu", a[0]);
		for (int i = 1; i <= a[0]; i++) {
			printf(" %llu", a[i]);
		}
		printf("\n%llu", b[0]);
		for (int i = 1; i <= b[0]; i++) {
			printf(" %llu", b[i]);
		}
		printf("\n%llu", c[0]);
		for (int i = 1; i <= c[0]; i++) {
			printf(" %llu", c[i]);
		}
		printf("\n");
	}
	return 0;
}

posted @ 2019-02-15 22:13  Artoriax  阅读(962)  评论(0编辑  收藏  举报