洛谷P1242 新汉诺塔(dfs,模拟退火)
最开始的思路是贪心地将盘子从大到小依次从初始位置移动到目标位置。
方法和基本的汉诺塔问题的方法一样,对于盘子 \(i\) ,将盘子 \(1\to i-1\) 放置到中间柱子上,即 \(6 - from - to\) 号柱子。基本递归实现。
但是贪心的优先将大盘移动到指定位置存在一些特殊情况处理错误。
例如如下数据,最优的方案并不是把大盘一步移到位,而是先移到了一根空柱上。
3
1 3
0
2 2 1
2 2 1
0
1 3
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<stdio.h>
#include<iostream>
#include<string>
#include<cstdlib>
#include<ctime>
using namespace std;
const int maxn = 50;
const int _time = 205;
const int inf = 0x3f3f3f3f;
int from[maxn], to[maxn], _from[maxn], _to[maxn];
int n, m, x, cnt, _cnt;
string ans, _ans, M[5] = {"0", "A", "B", "C"};
void dfs(int x, int a, int b)
{
if(a == b) return;
for(int i = x - 1; i >= 1; i--) dfs(i, from[i], 6 - a - b);
from[x] = b;
cnt++;
ans += "move ";
if(x>=10) ans += char(x / 10 + 48), ans += char(x % 10 + 48);
else ans += char(x + 48);
ans += " from "; ans += M[a]; ans += " to "; ans += M[b]; ans += "|";
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= 3; i++){
scanf("%d", &m);
for(int j = 1; j <= m; j++){
scanf("%d", &x); _from[x] = i;
}
}
for(int i = 1; i <= 3; i++){
scanf("%d", &m);
for(int j = 1; j <= m; j++){
scanf("%d", &x); _to[x] = i;
}
}
srand(time(0));
_cnt = inf;
for(int cas = 1; cas <= _time; cas++){
ans = ""; cnt = 0;
for(int i = 1; i <= n; i++) from[i] = _from[i], to[i] = _to[i];
for(int i = n; i >= 1; i--){
/***m模拟退火***/
if(rand() % (n - i + 2) == 0) dfs(i, from[i], 6 - from[i] - to[i]);
else dfs(i, from[i], to[i]);
}
for(int i = n; i >= 1; i--){
dfs(i, from[i], to[i]);
}
if(cnt < _cnt){
_cnt = cnt; _ans = ans;
}
}
for(int i = 0; i < _ans.size(); i++){
if(_ans[i] == '|') puts("");
else printf("%c", _ans[i]);
}
printf("%d\n", _cnt);
return 0;
}