Codeforces Problemset 98D(#78 div.1 D)

【题目大意】

某些盘子的直径相同的汉诺塔游戏,完成条件是按最初的顺序将盘子从第一个柱子移到第三个柱子。

【输入】

第一行一个数字n

第二行n个数字,从大到小给出盘子的半径。

【输出】

第一行一个数字,表示最少的移动次数m

接下来m行,每行两个数字表示一次操作,来描述具体操作过程。



这类题我一向不会做……看了题解明白了

x1表示半径最大的盘子数,x2表示半径次大的盘子数..xn表示半径最小的盘子数

那么用C(x1,x2,..,xn)表示将按顺序堆叠的盘子从一个柱子移到另一个柱子上(移过去以后按顺序)的最优解

U(x1,x2,..,xn)表示将按顺序堆叠的盘子从一个柱子移到另一个柱子上(移过去以后不一定按顺序)的最优解

有四条结论

1C (x1, x2, ..., xn) = U (x1, x2, ..., xn) if x1 = 1

2C (x1, x2, ..., xn) = 2*x1 - 1 if n = 1

3C (x1, x2, ..., xn) = U (x2, ..., xn) + x1 + U (x2, ..., xn) + x1 + C (x2, ..., xn)

4U (x1, x2, ..., xn) = U (x2, ..., xn) + x1 + U (x2, ..., xn)

来一一证明这四条结论
我们称最初盘子在的柱子为原柱,要移到的柱子为目标柱,剩下的那根柱子是辅助柱
1、 考虑U的过程,因为只有三根柱子,想要移动最大的圆盘只能把上面的盘子都移到辅助柱上(因为最大的圆盘不能放到别的上面),然后再将最大的移动到目标柱, 之后再将辅助柱上的所有圆盘移到目标柱上。可以发现只要将辅助柱上的圆盘移到目标柱时执行从目标柱移到辅助柱时的逆操作,那么这些盘子跟在原柱的时候顺序 是一样的,所以U过程跟C过程的区别是C过程需要把最下面那些直径有可能相同的圆盘按顺序叠放,而U过程直接移过去就好了(此时是对于这个直径的圆盘的逆 序),显然,当这个直径的圆盘只有一个的时候,U过程达到了跟C过程相同的结果。

2、这个结论挺显然的,把x1-1个移到辅助柱上,把最后一个移到目标柱上,然后再把辅助柱上的移到目标柱上。

3、这个是最一般情况下的C过程,把(x2,..,xn)移到目标柱上(顺序可能有变化),然后把x1移到辅助柱上(这时候显然是倒序),然后把(x2,..,xn)移回原柱,这时候只要执行移过来时的逆操作,那么可以移完的时候(x2,..,xn)跟最初的顺序是一样的,然后再把x1从辅助柱移到目标柱(这时候顺序就没问题了),再把(x2,..,xn)按顺序移到目标柱上。

4、说1的时候说过了。

有了这四个结论,很容易dp出来最少需要多少次。
然后仿照一般的汉诺塔(大家刚学的时候都写过吧)递归输出方案。
(没必要考虑什么逆操作输出,可以发现U过程的方案唯一,再搞一次即可)


#include <cstdio>

#include <iostream>

using namespace std;

int n,tot,i,j,k,s[21],g[21],f[21];

void U(int x,int a,int b,int c)

{

     if (x>n) return ;

     int i;

     U(x+1,a,c,b);

     for (i=1;i<=s[x];i++)

         printf("%d %d\n",a,b);

     U(x+1,c,b,a);

     return ;

}

void C(int x,int a,int b,int c)

{

     if (s[x]==1) U(x,a,b,c);

     else         

     if (x==n)

     {

        for (int i=1;i<s[x];i++)

            printf("%d %d\n",a,c);

        printf("%d %d\n",a,b);

        for (int i=1;i<s[x];i++)

            printf("%d %d\n",c,b);     

     }

     else

     {

         U(x+1,a,b,c);

         for (i=1;i<=s[x];i++)

             printf("%d %d\n",a,c);

         U(x+1,b,a,c);

         for (i=1;i<=s[x];i++)

             printf("%d %d\n",c,b);

         C(x+1,a,b,c);

     }

     return ;

}

int main()

{

    cin >> n;

    k=0;

    for (i=1;i<=n;i++)

    {

        cin >> j;

        if (j==k) s[tot]++;

        else      s[++tot]=1;

        k=j;

    }

    n=tot;

    g[n]=s[n];

    f[n]=s[n]*2-1;

    for (i=n-1;i>=1;i--)

    {

        g[i]=g[i+1]*2+s[i];

        if (s[i]==1) f[i]=g[i];

        else         f[i]=g[i+1]*2+s[i]*2+f[i+1];

    }

    cout << f[1] << endl;

    C(1,1,3,2);

    cin.get();

    cin.get();

    return 0;

}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值