洛谷 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);
}