洛谷 P1242 新汉诺塔 详细正解 搜索、概率论、递归

洛谷 P1242 新汉诺塔  详细正解

新汉诺塔

90分解法

分析

众所周知,汉诺塔是一道著名的递归例题,几乎所有人学习递归的第一道例题就是汉诺塔

汉诺塔移动的方法很简单,就是先移动大的,再移动小的

每次移动n号盘子的时候,将比n小的盘子都移动到中转柱上

代码

#include <iostream>
#include <cstdio>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 50;
int n, p1[maxn], p2[maxn], ans = 0;
void dfs(int p, int x, int y) {
	if (x == y) return;		//不需要转移
	for (int i = p - 1; i >= 1; i--)		//将比n小的盘子移动中转柱
		dfs(i, p1[i], 6 - x - y);
	p1[p] = y;
	printf("move %d from %c to %c\n", p, 'A' - 1 + x, 'A' - 1 + y);
	ans++;
}
int main() {
	scanf("%d", &n);	//输入数据
	int k, x;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p1[x] = 1;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p1[x] = 2;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p1[x] = 3;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p2[x] = 1;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p2[x] = 2;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p2[x] = 3;
	for (int i = n; i >= 1; i--)	//将盘子移动到目标位置
		dfs(i, p1[i], p2[i]);
	printf("%d\n", ans);
}

结果

概率论优化

被hack了

本来本题已经完美收官,但是luogu大佬们又加了test11,hack掉了传统的汉诺塔的解法

test:

3
1 3
0
2 2 1
2 2 1
0
1 3

out:

move 3 from A to B
move 1 from C to B
move 2 from C to A
move 1 from B to A
move 3 from B to C
5
传统的汉诺塔,初始的时候所有盘子都集中在一根柱子上,但是新汉诺塔所有的盘子分别在三个柱子上

所以盘子不一定直接移动到目标柱就是最优解,可以先移动到中转柱。

由于我们知道传统方法跑过来大多数点。所以,为了偷懒

我们通过概率论优化,(模拟退火)

就是多跑几遍,选择不同的转移方案,即每次不一定直接转移到目标柱

多跑几遍,这样就能混过这道题了;

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
struct Move{		//移动方式
	int data;
	int start, end;
};
const int maxn = 50;
int n, p1[maxn], p2[maxn], ret = 1e9; Move moves[1000000];	//原始数据
int f1[maxn], f2[maxn], ans = 0; Move m[1000000];	//测试数据
void dfs(int s, int x, int y) {
	if (x == y) return;
	for (int i = s - 1; i >= 1; i--)
		dfs(i, f1[i], 6 - x - y);
	f1[s] = y;
	ans++;
	m[ans].data = s; m[ans].start = x; m[ans].end = y;
}
int main() {
	scanf("%d", &n);
	int k, x;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p1[x] = 1;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p1[x] = 2;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p1[x] = 3;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p2[x] = 1;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p2[x] = 2;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p2[x] = 3;
	for (int i = 1; i <= 233; i++) {		//多跑几次
		ans = 0;			//初始化数据
		for (int j = 1; j <= n; j++) {
			f1[j] = p1[j];
			f2[j] = p2[j];
		}
		for (int j = n; j >= 1; j--) {	//可能不移动到目标柱
			if (rand() % (j + 1)) dfs(j, f1[j], f2[j]);		
			else dfs(j, f1[j], 6 - f1[j] - f2[j]);
		}
		for (int j = n; j >= 1; j--)	//前面不一定移动到目标柱,最后补上
			dfs(j, f1[j], f2[j]);
		if (ans < ret) {
			for (int i = 1; i <= ans; i++)
				moves[i] = m[i];
			ret = ans;
		}
	}
	for (int i = 1; i <= ret; i++)
		printf("move %d from %c to %c\n", moves[i].data, 'A' - 1 + moves[i].start, 'A' - 1 + moves[i].end);
	printf("%d\n", ret);
}

结果

正解

分析

我们知道当所有盘子都集中在一根柱子上时,那么将盘子移动到目标柱即为最优解

新汉诺塔中,虽然初始不在一个柱上,但当我们将n号盘子移动到目标柱时,1----n-1号盘子都将移动中转柱上

那么1-----n-1号盘子直接移动到目标柱上即可。

n号盘子,可以先移到中转柱,再移动到目标柱,也可以直接移动到目标柱

我们将两种方法都跑一遍,取最优即可。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#pragma warning (disable:4996)
struct Move{
	int data;
	int start, end;
};
const int maxn = 50;
int n, p1[maxn], p2[maxn], ret = 1e9; Move moves[1000000];
int f1[maxn], f2[maxn], ans = 0; Move m[1000000];
void dfs(int s, int x, int y) {
	if (x == y) return;
	for (int i = s - 1; i >= 1; i--)
		dfs(i, f1[i], 6 - x - y);
	f1[s] = y;
	ans++;
	m[ans].data = s; m[ans].start = x; m[ans].end = y;
}
int main() {
	scanf("%d", &n);
	int k, x;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p1[x] = 1;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p1[x] = 2;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p1[x] = 3;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p2[x] = 1;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p2[x] = 2;
	scanf("%d", &k); for (int i = 1; i <= k; i++)scanf("%d", &x), p2[x] = 3;
	srand(1295103112);
	for (int i = 1; i <= 2; i++) {
		ans = 0;
		for (int j = 1; j <= n; j++) {
			f1[j] = p1[j];
			f2[j] = p2[j];
		}
		if (i & 1) dfs(n, f1[n], f2[n]);	//直接移动到目标柱上
		else dfs(n, f1[n], 6 - f1[n] - f2[n]);	//先移动到中转柱上
		for (int j = n - 1; j >= 1; j--)
			dfs(j, f1[j], f2[j]);
		if ((i & 1) == 0)			//将n放回目标柱
			for (int i = n; i >= 1; i--)
				dfs(i, f1[i], f2[i]);
		if (ans < ret) {
			for (int i = 1; i <= ans; i++)
				moves[i] = m[i];
			ret = ans;
		}
	}
	for (int i = 1; i <= ret; i++)
		printf("move %d from %c to %c\n", moves[i].data, 'A' - 1 + moves[i].start, 'A' - 1 + moves[i].end);
	printf("%d\n", ret);
}

结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值