uva 10795 新汉诺塔问题

来源:互联网 发布:李涛淘宝 编辑:程序博客网 时间:2024/06/06 21:56

题目地址:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1736

有n个大小不一样的盘子和3个柱子。给定一个初始局面,求到给定目标局面至少需要多少步。
移动规则:一次只能移一个盘子,在移动一个盘子之前,必须把压在上面的其他盘子先移走,编号大的盘子不得压在编号小的盘子上。

这道题目需要一点想象空间啊确实,给出图片方便想象:
这里写图片描述

分析:
1.考虑编号最大的盘子,如果它在初始局面和目标局面位于同一个柱子,那么不需要移动它。
这样我们在初始局面和目标局面中,找到所在柱子不同的编号最大的那个盘子,设为k(下面用到的k都是同样的意思),k必须移动。
在题目图片中,如果上面是初始局面下面是目标局面的话,k=3;调换过来的话,k。。还是3。

2.在k移动前。假设k需要从柱子A移动到柱子B,由于编号比k大的盘子不需要移动,所以直接当作他们不存在。
编号比k小的既不能在A上,也不能在B上,所以只能在C上。
这时候,A只有盘子k,B没有盘子(比k大的不算),C从上到下依次是盘子1,2,3…k-1。
我们把这个局面称作参考局面。

3.由于盘子移动是可逆的,根据对称性,我们只需要求出初始局面和目标局面移动成参考局面的步数之和,然后加1(移动盘子k)即可。
注意,这里初始到参考的参考局面和目标到参考的参考局面是不一样的。如果初始到参考的参考局面是k从柱子A到B,那么目标到参考的参考局面就是k从柱子B到A。(拿个例子看看就知道了)
然后我们需要写一个函数f(P,i,final),P是各盘子初始局面的柱子编号数组(P[i]是盘子i的柱子编号),函数的值是将i个盘子移动柱子final的所需步数。
那么本题答案:f(start,k-1,6-start[k]-final[k]) + f(final,k-1,6-start[k]-final[k]) + 1 其中start[i]和final[i]就是i盘子的初始柱子和目标柱子,k就是要移动的盘子的最大编号。
因为柱子编号为1,2,3,所以剩下的那个柱子编号就是6-start[k]-final[k]。

4.接下来要计算f(P,i,final)。
(1)如果P[i] = final ( 意思是该移动的编号i已经在柱子final上),那么f(P,i,final)=f(P,i-1,final)。
(2)如果P[i] ≠ final,那么需要把前i-1个盘子移动到6-P[i]-final这个柱子作为中转,然后把i移动到柱子final上,最后在把i-1个盘子移动到柱子final上。
最后一步是把i-1个盘子从一个柱子移到另一个柱子上(旧汉诺塔问题),这个步骤需要 (2∧i-1)-1 步(如将3个盘子从A移到B,需要7步)。加上移动盘子i的那一步,就是2∧i-1。
那么答案就是 f(P,i,final) = f(P,i-1,6-p[i]-final) + 2∧i-1。

5.最后答案要用long long保存。因为int是32位,n<=60。2∧i-1会超。

6.然后贴一个旧汉诺塔问题的链接:
http://blog.csdn.net/liujian20150808/article/details/50793101

#include<cstdio>long long f(int *P, int i, int final){    if (i == 0)        return 0;    if (P[i] == final)        return f(P, i - 1, final);    return f(P, i - 1, 6 - P[i] - final) + (1LL << (i - 1));}const int maxn = 60 + 10;int n, start[maxn], finish[maxn];int main(){    int kase = 0;    while (scanf("%d", &n) == 1 && n)    {        for (int i = 1; i <= n; i++)            scanf("%d", &start[i]);        for (int i = 1; i <= n; i++)            scanf("%d", &finish[i]);        int k = n;        while (k >= 1 && start[k] == finish[k])            k--;        long long ans = 0;        if (k >= 1)        {            int other = 6 - start[k] - finish[k];            ans = f(start, k - 1, other) + f(finish, k - 1, other) + 1;        }        printf("Case %d: %lld\n", ++kase, ans);    }    return 0;}
0 0