题解 P1242 【新汉诺塔】
这里我给大家带来一个完全正确且经过证明的方法,最后ac时间为3ms左右,还有很多值得优化的地方。
我们一起来思考一下怎么移
首先肯定是先把大盘子移到位,否则小盘子还是要动的,假设现在最大的盘子n要从A移动到B,有两种移法可能是最简便的
1、比n小的都依次移到C上,再把n移到B
2、或者把n小的都依次移到B上,再把n移到C,然后把那一堆小的移到A,把n移到B
绝大多数情况下都是第一种方案更优
为什么说绝大多数呢?因为第一种方案最后一个点是过不去的,最后一个点第二种方案更优。
然鹅特别的,有一种特殊情况,当需要移动的那一堆都在一个柱子上时,1方案一定最优,也就是说除了第一步外其他几步都是1方案最优
所以只需第一步进行一次2方案,剩下都用1方案,再进行比较就行了,不会超时,只是原方法的三倍时长
下面来证明一下这个特殊情况
最后给出代码
由于我写的很多地方都是重复的,所以代码比较长,而且很多地方可以优化
#include<iostream>
#include<string>
#include<algorithm>
#include<cmath>
#define Endl endl
using namespace std;
int present[46], present1[46], present2[46]; //现在圆盘的状态即所在的柱子位置
int dest[46]; //目标状态
int n;
int Count,Count1,Count2;
void swap(int i, int pos1, int pos2) //圆盘编号i,在第pos1的柱子上,要移到pos2的柱子上
{
if (i == 1) //只剩下最小的1号盘子
{
present[i] = pos2;
cout << "move 1 from " << char('A' + pos1 - 1) << " to " << char('A' + pos2 - 1) << endl;
Count++;
return;
}
for (int j = i - 1; j >= 1; j--) //把剩下的盘子都放到这两个柱子之外的那个柱子
{
if (present[j] != 6 - pos1 - pos2)
{
swap(j, present[j], 6 - pos1 - pos2);
}
}
present[i] = pos2; //移动这个大盘子
cout << "move " << i << " from " << char('A' + pos1 - 1) << " to " << char('A' + pos2 - 1) << endl;
Count++;
}
void swap_count1(int i, int pos1, int pos2) //圆盘编号i,在第pos1的柱子上,要移到pos2的柱子上
{
if (i == 1) //只剩下最小的1号盘子
{
present1[i] = pos2;
//cout << "move 1 from " << char('A' + pos1 - 1) << " to " << char('A' + pos2 - 1) << endl;
Count1++;
return;
}
for (int j = i - 1; j >= 1; j--) //把剩下的盘子都放到这两个柱子之外的那个柱子
{
if (present1[j] != 6 - pos1 - pos2)
{
swap_count1(j, present1[j], 6 - pos1 - pos2);
}
}
present1[i] = pos2; //移动这个大盘子
//cout << "move " << i << " from " << char('A' + pos1 - 1) << " to " << char('A' + pos2 - 1) << endl;
Count1++;
}
void swap_count2(int i, int pos1, int pos2) //圆盘编号i,在第pos1的柱子上,要移到pos2的柱子上
{
if (i == 1) //只剩下最小的1号盘子
{
present2[i] = pos2;
//cout << "move 1 from " << char('A' + pos1 - 1) << " to " << char('A' + pos2 - 1) << endl;
Count2++;
return;
}
for (int j = i - 1; j >= 1; j--) //把剩下的盘子都放到这两个柱子之外的那个柱子
{
if (present2[j] != 6 - pos1 - pos2)
{
swap_count2(j, present2[j], 6 - pos1 - pos2);
}
}
present2[i] = pos2; //移动这个大盘子
//cout << "move " << i << " from " << char('A' + pos1 - 1) << " to " << char('A' + pos2 - 1) << endl;
Count2++;
}
int main()
//首先肯定是先移大盘子,假设n要从A移动到B,有两种移法
//1、比n依次小的都移到C上,再把n移到B
//2、或者把n依次小的都移到B上,再把n移到C,然后把那一堆移到A,把n移到B
//绝大多数情况下都是第一种方案更优
//特别的,当需要移动的那一堆都在一个柱子上时,1方案一定最优
//所以只需第一步进行一次2方案,剩下都用1方案,再进行比较就行了,不会超时
{
cin >> n;
int t2; //圆盘编号
int t1; //柱上圆盘数
for (int j = 1; j <= 3; j++) //初始化圆盘的开始位置
{
cin >> t1;
for (int i = 0; i < t1; i++)
{
cin >> t2;
present[t2] = j;
}
}
for (int i = 1; i <= n; i++)
{
present1[i] = present2[i] = present[i];
}
for (int j = 1; j <= 3; j++) //初始化圆盘的目标位置
{
cin >> t1;
for (int i = 0; i < t1; i++)
{
cin >> t2;
dest[t2] = j;
}
}
for (int i = n; i >= 1; i--) //一方案
{
if (present1[i] != dest[i])
{
swap_count1(i, present1[i], dest[i]);
}
}
for (int i = n; i >= 1; i--) //二方案初始化
{
if (present2[i] != dest[i])
{
swap_count2(i, present2[i], 6 - present2[i] - dest[i]);
break;
}
}
for (int i = n; i >= 1; i--) //二方案
{
if (present2[i] != dest[i])
{
swap_count2(i, present2[i], dest[i]);
}
}
if (Count1 <= Count2)
{
for (int i = n; i >= 1; i--) //一方案
{
if (present[i] != dest[i])
{
swap(i, present[i], dest[i]);
}
}
}
else
{
for (int i = n; i >= 1; i--) //二方案初始化
{
if (present[i] != dest[i])
{
swap(i, present[i], 6 - present[i] - dest[i]);
break;
}
}
for (int i = n; i >= 1; i--) //二方案
{
if (present[i] != dest[i])
{
swap(i, present[i], dest[i]);
}
}
}
cout << Count << Endl;
return 0;
}