zoukankan      html  css  js  c++  java
  • 杭电 汉诺塔问题总结

    看了一下杭电的各种汉诺塔问题,遇到些奇奇葩葩的小问题,也有很多很好的思想,比如最后一题,来来回回的颠倒很有意思。总结一下;

    Pro.ID 1207 :http://acm.hdu.edu.cn/showproblem.php?pid=1207

    意思是给把原始的汉诺塔问题中的3根柱子改为4根。做了半天各种WA。查了一下,有一篇文章详细讲了一下,还做出了递归公式以及数学公式:

    地址如下:http://www.cnblogs.com/fanzhidongyzby/archive/2012/07/28/2613173.html.

    代码如下:

    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    //F(n)=min(2*F(n-r)+2^r-1),(1≤r≤n)
    using namespace std;
    int main()
    {
        int n,m;
        long long Min,f[65];
        f[1]=1;
        f[2]=3;
        for(int i=3;i<=65;i++)
        {
            Min=99999999;
            for(int j=1;j<i;j++)
                Min=min(2*f[j]+pow(2.0,i-j)-1,Min*1.0);
            f[i]=Min;
        }
        while(~scanf("%d",&n))
            printf("%lld
    ",f[n]);
        return 0;
    }


    按照公式写就是了,写的时候需要注意一下精度问题。2^r可以写为1<<r,不过因为数字常数1默认是32位,所以如果要使用位移的话,一定要先声明一个longlong类型的变量来进行位移,否则 就会出现溢出错误,这个我纠结了一阵子,感觉没错,一提交就WA,然后试了试64发现果然是负数。

    哎,基础还是不扎实啊。

    用pow函数因为返回的是一个double类型,min函数里比较也是用double来做的,只是在最后赋值的时候取int型就可以,所以不会出错。


    Pro.ID  1995 汉诺塔V

    http://acm.hdu.edu.cn/showproblem.php?pid=1995

    这个是普通的汉诺塔,最优的步数是2^n-1,只不过问的第i个盘子移动的次数。依然是用递归,在纸上画画就能出来。

    注意了,第i盘子,不用考虑底下的盘子,只用看之上的经过一个柱子到达目的地。即F[n]=2*f[n-1]

    代码:

    #include<iostream>
    
    using namespace std;
    
    int
    main()
    {
        __int64 s[61] = {0, 1};
        int n, i, t, m;
    
        for(i = 2; i < 61; i++)
            s[i] = s[i - 1] * 2;
    
        cin >> t;
        while(t--)
        {
            cin >> n >> m;
    
            cout << s[n - m + 1] << endl;
        }
    }

    Pro.ID 汉诺塔VI

    http://acm.hdu.edu.cn/showproblem.php?pid=1996

    是问所有步骤,注意不是最优的,是全部(当然不包括错误的步骤)

    每一个盘子可以放到3根柱子的任意一个,所以是3^n。比如正确的是直接从a->c,现在可以a->b然后在b->c,就是多了2种。每一个都多了2种,所以是3^n。

    代码:

    #include<iostream>
    #include<math.h>
    #include<stdio.h>
    using namespace std;
    int main()
    {
        __int64 t,n,i;
        __int64 sum,a[100]={3,};
        while(cin>>t)
        while(t--)
        {
            cin>>n;
            for(i=1;i<n;i++)
            a[i]=a[i-1]*3;
            cout<<a[n-1]<<endl;
        }
        return 0;
    }

    Pro.ID 1997 汉诺塔VII

    http://acm.hdu.edu.cn/showproblem.php?pid=1997

    题目是说,给定某一时刻的三个柱子上的盘子,问这个是不是符合最优解过程中某一时刻的状态。

    思想是:

    对一个含有n个盘子,从A柱移动到C柱借助B柱的汉诺塔,第n个盘子移动到C柱过程是这样子的:首先将其余的n-1个盘子移动到B柱,然后第n个盘子直接移动到C柱。在这过程中,第n个盘子只出现在A柱和C柱两个柱子上,也即第n个盘子不可能出现在B柱上。因此对于当前移动的盘子,只需检查其所在的柱子是否符合这个要求,如果出现在B柱上,则显然进入了错误移动中。这是本题求解思想精髓所在。

    具体的内容请参考这篇博客:http://blog.csdn.net/z690933166/article/details/8605261

    代码就不贴了,上边这个博客里写的很详细。

    Pro.ID 2064 汉诺塔III

    http://acm.hdu.edu.cn/showproblem.php?pid=2064

    还是递推:num[i]=3*num[i-1]+2;

    不解释了,代码如下:

    #include<iostream> 
    #include<cstdio>  
    #include<cstring> 
    #include<cmath>    
    using namespace std;      
    unsigned long long num[36];
    int main()
    {              
        num[1]=2;
        num[2]=8;
        for(int i=3;i<=35;i++)
            num[i]=3*num[i-1]+2;
        int n;
        while(~scanf("%d",&n))
            cout<<num[n]<<endl;
        return 0;
    }


    Pro.ID 2077 汉诺塔IV(参考了 zz_zigzag的博客)

    http://acm.hdu.edu.cn/showproblem.php?pid=2077

    好无聊啊,把上边的规则给改了,只是最大的可以放上边。其实感觉这个题目跟之前那个1027题目有点想通之处,在1027中说的是4根柱子,所以通用的这3个步骤其实并非最优解:

    (1)       1柱借助3…M柱子将n-(M-2)个盘子移动到2柱上。

    (2)       M-2个通过3…M-1柱简单的移动到M柱上【2*(M-2)-1步骤】。

    (3)       2柱借助13…M-1柱子将n-(M-2)个盘子移动到M柱上。

    如果有n个盘子,则需要前n-1个挪到中间的盘子上,然后最大的盘子挪到最右面,需要两步,把前(n-1)个盘子从左边挪到中间是和从中间挪到右边需要相同的次数。而a数组中存放的就是那个前n-1个盘子挪动到相同位置需要的次数。结果即为a[i-1]*2+2。

    所以我直接想成了是f[n]=2*f[n-1]+2,结果错了。【因为是需要n-1个盘子前进一步】


    而求a数组需要用到递推。公式为第i个为前n-1个移动次数的三倍加一,简化到两个盘子,小的先移动两次到最右边,大的移到中间,然后小的在移回中间,小的移动了三次,而大的移动了一次,就使他们全部挪动了一个位置

    所以代码如下:

    #include<stdio.h>
    int a[20]={0,1};
     
    int main()
    {
        int i,T;
        for(i=2;i<21;i++)
        {
            a[i]=3*a[i-1]+1; 
        }
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d",&i);
            printf("%d
    ",2*a[i-1]+2);
        }
        return 0;
    }


    Pro.ID 2175 汉诺塔IX

    http://acm.hdu.edu.cn/showproblem.php?pid=2175

    普通汉诺塔,问在最优步骤中的第i步是哪一个盘子,跟1995那个题目刚好相反。不过这个有点像数论题。

    这样想,假设是4个盘子,考虑第三个,在第4步的时候将3盘从A移动到C【设目的地是B】,此时1,2盘在B上,设时间为T,然后将1,2盘移动到C上,(需要3步)再把4盘移动到B上,此时的格局为4盘在B上,1,2,3,在C上,距T过去了1+3=4步,那么3号盘什么时候再动呢?把1,2移走,3就可以放到B上了,移走1,2需要花费3个步骤,因此距T4+3+1也就是第8步,总体是第12步时,3号盘子会再次移动。现在看明白了吧,就是基数倍的2^(i-1)时,i号盘子会移动。

    代码如下:

    #include<iostream>
    using namespace std;
    int main()
    {
        __int64 a[65];
        a[1]=1;
        __int64 i,n,r;
        __int64 m;
        for(i=2;i<=63;i++)
            a[i]=2*a[i-1];
        while(~scanf("%I64d%I64d",&n,&m),n+m)
        {
            for(i=1;i<=n;i++)
            {
                r=m/a[i];
                if(r%2==1&&m%a[i]==0)
                    printf("%I64d
    ",i);
            }
        }
        return 0;
    }


    Pro.ID 2184 汉诺塔VIII

    感觉这个题目非常的好,挺有意思的,问普通汉诺塔,N个盘子,在最优解的第M步时,每个柱子上的盘子的状态。

    想了半天,也没什么思路,但有一点是绝对可以确定的,就是一般解简单汉诺塔过程的问题都是使用递归,可以得到全部过程,但是当N稍微到10以上的时候必然递归很慢,所以直接递归模拟必然是错误的,但是根据上一题目,第i个步移动哪一个盘子中确定的,第K个盘子在  奇数*2^(K-1)时移动可以得到些思想,必然是根据步数来确定盘子。

    但是想了老半天也不太清楚那个递归改怎么写,好像每一次判断都要做除法到是可以确定某一个盘子,但是如何确定所有的盘子呢?纠结啊

    查了半天,找到了一个大神的代码。

    地址在:http://blog.lchx.me/index.php/hdu-2184-%E6%B1%89%E8%AF%BA%E5%A1%94viii/

    讲的非常的详细,我把思路以及代码粘过来大家分享一下:

    /*
    
    定义数组a,其中a[i]表示完成一次i层汉诺塔移动的次数。
    指针o,p,q分别表示三个位置。
    起始状态为n层都在o上,要往q方向移动。
    然后分成两种情况:
    1、
    m<=a[n-1];
    此时,第n层没机会移动,那么就相当于o上的n-1层往p上移动。
    使其状态和起始状态一致,我们要交换p和q。
    2、
    m>a[n-1];
    此时,先进行到下面状态,上面n-1层移动到p位置,第n层移动到q位置,消耗了a[n-1]+1次移动。
    接下来就变成p上的n-1层往q上移动,只要交换o,p,令m=m-a[n-1]-1即可。
    
    通过上述操作,都可以得到第n层的位置,并且问题变成n-1层都在o上,要往q方向移动。
    */
    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    using namespace std;
    int main()
    {
    	unsigned __int64 m,a[64];
    	int row[3][66];
    	a[1]=1;
    	a[0]=0;
    	for(int i=2;i<=63;i++)
    		a[i]=a[i-1]*2+1;
    	int t;
    	scanf("%d",&t);
    	while(t--)
    	{
    		int n;
    		scanf("%d %I64u",&n,&m);
    		int *start,*mid,*end;
    		start=row[0];
    		mid=row[1];
    		end=row[2];
    		*start=*mid=*end=1;
    		while(n)
    		{
    			n--;
    			if(m<=a[n])
    			{
    				*(start+*start)=n+1;//从第二个位置开始记录盘子
    				(*start)++;//第一个位置表示的是这个柱子一共有多少个盘子
    				swap(end,mid);
    			}
    			else
    			{
    				*(end+*end)=n+1;
    				(*end)++;
    				swap(start,mid);
    				m-=(a[n]+1);
    			}		
    		}
    		for(int i=0;i<3;i++)
    		{
    			printf("%d",row[i][0]-1);
    			for(int j=1;j<row[i][0];j++)
    				printf(" %d",row[i][j]);
    			printf("
    ");
    		}
    	}
    	return 0;
    }



    Pro.ID 汉诺塔 X

    http://acm.hdu.edu.cn/showproblem.php?pid=2511

    进一步加强条件,在求第m步时是哪个盘子动,怎么动。

    必然递归啊。把上上个题目修改就可以了。具体的就不多说了,在注释里有详细解释

    #include<iostream>
    using namespace std;
     __int64 a[65];
    void solve(int n,__int64 m,int start,int end)
    {
    	int third=6-start-end;//得到第3跟柱子
    	__int64 mid=a[n];
    	if(m==mid) //如果是当前盘子移动,直接从start-->end
    	{
    		printf("%d %d %d
    ",n,start,end);
    		return ;
    	}
    	if(m<mid)//当前盘子无法移动,必然是上边的某个盘子动,并且移动一定是到third号柱子上,递归求解
    		solve(n-1,m,start,third);
    	else//需要先移动当期盘子下部的盘子(参考2184题目)
    		solve(n-1,m-mid,third,end);
    }
    		
    int main()
    {
        __int64 m;
        a[1]=1;
    	int n;
        for(int i=2;i<=63;i++)
            a[i]=2*a[i-1];
    	int t;
    	while(~scanf("%d",&t))
    	{
    		while(t--)
    		{
    			scanf("%d%I64d",&n,&m);
    			solve(n,m,1,3);
    		}
    	
    	}
    	return 0;
    }

    最后一个Pro.ID  2587:很O_O的汉诺塔

    http://acm.hdu.edu.cn/showproblem.php?pid=2587

    真心是跪了,

    感谢hr_whisper的详细讲解,这里已经写的很清楚了:http://blog.csdn.net/murmured/article/details/9943947

  • 相关阅读:
    swift 第十四课 可视化view: @IBDesignable 、@IBInspectable
    swift 第十三课 GCD 的介绍和使用
    swift 第十二课 as 的使用方法
    swift 第十一课 结构体定义model类
    swift 第十课 cocopod 网络请求 Alamofire
    swift 第九课 用tableview 做一个下拉菜单Menu
    swift 第八课 CollectView的 添加 footerView 、headerView
    swift 第七课 xib 约束的优先级
    swift 第六课 scrollview xib 的使用
    swift 第五课 定义model类 和 导航栏隐藏返回标题
  • 原文地址:https://www.cnblogs.com/pangblog/p/3265204.html
Copyright © 2011-2022 走看看