HDU2.2.3 汉诺塔VII

这道题卡了我好几天,不过最后还是想出来了,我用的是迭代的方法,效率上要比网上查到的递归DFS要高一些,但是却不如那种方法直观。

现在我把两种方法都放上来好了。

基本思路都是一样的:先自己在草稿纸上画画汉诺塔的移动方法,然后再找规律。

我们可以发现,对于N个盘子,最开始是在A柱上,然后之前离散课上分析所需步骤数的时候是转化成较小的子问题,即把N-1个盘子通过某种方法移动到B柱上,然后再把第N个盘子(即最大的盘子)移动到C柱上。对于N-1个盘子的子问题,又可以把这N-1个盘子中的N-2个通过某种方法转移到A柱上,再把第N-1个盘子转移到C柱上。这里是关键:我们对比两个过程,刚开始是N个盘子在A柱上,但是当转化成N-1的问题时,这N-1个盘子是在B柱上,那么我们可以把这时的B柱就看成初始的A柱。

而且,我们在移动的时候绝对不会把当前最大的盘子放到“相对的”B柱上(这个B柱是相对于把初始的盘子所在的柱看成A柱而言),因为这是不必要的,我们的最后一步都是直接把最大的盘子直接从最开始在的柱子转移到目标柱子,中间那根“相对B柱”只是给另外的那些盘子过渡使用。

于是,对于N,我们都先检查其是否在“相对”B柱上,千万注意这里的B柱不是定死的,而是根据我们问题开始时候的初始状态决定的,比如最开始盘子都在A上,那么B柱自然也是“相对B柱”,但是当经过一轮后,我们把问题转化成了N-1的问题,这时候这N-1个盘子的初始状态是在B柱,那么我们仍旧把现实中的B柱视作“A柱”,而把现实中的A柱视作“B柱”。

再次回归之前说到的检查N,是否在“相对B柱”上,如果是,那么一定是不可能的,直接把flag标记为0然后break即可。但是如果N号盘子在“相对A柱”上呢?这是有可能发生的,于是还要进一步检查。为了方便起见,我们后面所说的A柱、B柱之类都是相对而言的,并不是定死的。因为N号盘子在A上,所以N-1号一定是在过渡的B上,而不可能在C上。同理,如果N号盘子在C上,那么N-1号也仍旧在过渡的B上,而不可能在A上。所以在下面代码的satisfy函数中我们分了三种情况来讨论,但是特别注意的一点是,每当把问题缩小一个规模,就要把柱子的相对编号给改变一下,比如如果N号盘子在A上,那么在检查N-1号是否在3上后,还要把B与C的标号换一下,因为我们对N-1个盘子的目标已经不是现实中的C而是现实中的B了,而为了统一起见,我们把B和C互换一下即可。

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

int m[65],p[65],q[65];
int map[65];

bool satisfy(int n)
{
	if(map[n]==2)
		return 0;
	else if(map[n]==1){
		bool temp=map[n-1]!=3;
		for(int i=1;i<=n;i++){
			if(map[i]==2)
				map[i]=3;
			else if(map[i]==3)
				map[i]=2;
		}
		return temp;
	}
	else if(map[n]==3){
		bool temp=map[n-1]!=1;
		for(int i=1;i<=n;i++){
			if(map[i]==1)
				map[i]=2;
			else if(map[i]==2)
				map[i]=1;
		}
		return temp;
	}
}

int main()
{
	int T;
	cin>>T;
	while(T--){
		int n;
		memset(map,0,sizeof(map));
		scanf("%d",&n);
		scanf("%d",&m[0]);
		for(int i=1;i<=m[0];i++){
			scanf("%d",&m[i]);
			map[m[i]]=1;
		}
		scanf("%d",&p[0]);
		for(int i=1;i<=p[0];i++){
			scanf("%d",&p[i]);
			map[p[i]]=2;
		}
		scanf("%d",&q[0]);
		for(int i=1;i<=q[0];i++){
			scanf("%d",&q[i]);
			map[q[i]]=3;
		}
		bool flag=1;
		for(int i=n;i>=2;i--){
			if(satisfy(i)==0){
				flag=0;
				break;
			}
		}
		if(flag)
			printf("true\n");
		else
			printf("false\n");
	}
	return 0;
}

 

不过感觉自己还是太差了,DFS还是不怎么会写,这里引用一下他人的DFS的分析吧:

我们知道递归求汉诺塔步数时,了解首先我们要将A上的1~(n-1)个移到B上,然后将A上的第n大的移到C上,就完成了第一步。然后我们需要将B上的n-1个盘移到C上,可知现在可以将B看做之前的A,A看做B。

现在,我们需要判断给出的状态是否为正确的移动(移动步数最短的移法)中的状态。

因为递归求步数时,第二步可以实现必须是n已经移动到了C上,若n仍然在A上,则此时要求的就是将A上的n-1个移动到B上,即此时n-1按正确移法可能还在A上,也可能已经到了B上。

所以每次n可以出现在A‘或C’上,(1)若n出现在A'上则此时变成n-1的问题(此时的B‘变成C'',A’仍是A‘,C'变成B’‘),(2)若n出现在C'上,则为第二步,将B’上的n-1个通过移动n-2个到A'上,将第n-1移动到C‘上(即B'变成A'',A''变成B’‘,C仍然是C)。否则为错误移法(多余的移动)。

#include<iostream>
using namespace std;

bool flag;
void DFS(int n,int *A,int *B,int *C){
    if(n==0){
        flag = true;
        return ;
    }
    if(B[0]&&n==B[1]){
        flag = false;
        return ;
    }
    if(A[0]&&n==A[1]){
        A[1]=A[0]-1;
        DFS(n-1,++A,C,B);
    }else if(C[0]&&n==C[1]){
        C[1]=C[0]-1;
        DFS(n-1,B,A,++C);
    }
}
int main(){
    int A[70],B[70],C[70];
    int T,n;
    cin>>T;
    while(T--){
        cin>>n;
        cin>>A[0];
        for(int i=1;i<=A[0];i++)
            cin>>A[i];
        cin>>B[0];
        for(int i=1;i<=B[0];i++)
            cin>>B[i];
        cin>>C[0];
        for(int i=1;i<=C[0];i++)
            cin>>C[i];
        DFS(n,A,B,C);

        if(flag)
        cout<<"true"<<endl;
        else
        cout<<"false"<<endl;
    }
}



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值