UVa10795 - A Different Task(递归)

题目链接

简介:新汉诺塔问题(给出了初始状态和目标状态)

分析:
我们考虑最大的盘子,如果这个盘子初始状态和中止状态在一个柱子上,说明我们根本不用移动ta
那么我们找到编号最大的需要移动是盘子k(初始状态所在的柱子和终止状态不一样)
假设k要从x—>y,那么在移动k之前三根柱子的状态一定是(我们可以忽略那些编号大于k且不需要移动的柱子):
x:k
y:空
z:1~k-1有序摆放
我们称这种状态为中间状态

由于盘子的移动是可逆的,根据对称性,
问题所需要的步数就是:(初始状态到达中间状态的步数)+(终止状态到达中间状态的步数)+1

汉诺塔问题有一个经典结论,不得不提一下:

把n个有序的盘子由一根柱子全部移动到另一根柱子上,所需的步数是:2^n-1

这就是一个递归的过程,
递归的柿子就可以表示为:f(start,i,final)=f(start,i-1,6-final-start[i])+f(finish,i-1,6-final-start[i])+1
start表示初始状态,finish表示终止状态,final表示需要移动到的柱子
我们把柱子依次编号1,2,3,那么除了初始状态和终止状态,剩下的那根柱子的编号就是6-final-finish[i]

f(P,now,final)表示当前的状态是P,我们需要把1~now个盘子全都移动到final上

怎么计算f(P,now,final)
如果当前的盘子now已经在目标柱子final上了,那f(P,now,final)=f(P,now-1,final)
如果不在目标柱子上,我们就要分成三步:

  • 把now-1个盘子移动到(6-final-P[now])
  • 把第now个盘子移动到final,需要一步
  • 把now-1个盘子从(6-final-P[now])移动到final,注意这里是把now-1个盘子全部按顺序移动到final
    根据汉诺塔问题的经典结论,这需要(2^(now-1)-1)

所以f(P,now,final)=f(P,now-1,6-final-P[now])+2^(now-1)

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

int n;
int start[103],finish[103];

ll f(int *P,int now,int final)
{
    if (!now) return 0;
    if (P[now]==final)
       return f(P,now-1,final);
    return f(P,now-1,6-final-P[now])+(1LL << (now-1));
}

int main()
{
    int cnt=0;
    while (scanf("%d",&n)!=EOF&&n)
    {
        for (int i=1;i<=n;i++) scanf("%d",&start[i]);
        for (int i=1;i<=n;i++) scanf("%d",&finish[i]);

        int i=n;
        while (i>=1&&finish[i]==start[i]) i--;

        ll ans=0;
        if (i>=1)
        {   
            int t=6-start[i]-finish[i];
            ans=f(start,i-1,t)+f(finish,i-1,t)+1;
        }

        printf("Case %d: %lld\n",++cnt,ans);
    }
    return 0;
}
posted @ 2017-10-15 10:29  wtt3117  阅读(73)  评论(0编辑  收藏