Hanoi Tower: 变形/总结

省流:没有更新完成,正在慢慢更新。

Hanoi Tower 问题本身很简单,A,B,C 三个柱子,起初每一个圆盘都在 A 上,想要全部移动到 B/C。每次只能移动最上面的,大的在小的圆盘下面。

Original Problem/原问题

考虑一个递归函数。\(hanoi(n,A,B,C)\) 代表目标是把 \(N\) 这个大小的盘子通过 B 从 A 移动到 C。

void hanoi(int n,char A,char B,char C){
	if (n){
		hanoi(n-1,A,C,B);
		cout<<A<<"->"<<C<<endl;
		hanoi(n-1,B,A,C);
	}
}

步数是 \(2^n-1\)。总共状态是 \(2^n\) 个(包含最前面的)。

  • 最优是 \(2^n-1\)。对于一个盘子 \(x\),它的希望是 \(1\cdots x-1\) 都从它身上下来,它移动一下,\(1\cdots x-1\) 都再爬到他身上。没有步数的浪费,所以是最优。

  • 总共状态是 \(2^n\) 个。最多 \(2^n\) 个。如果有两个重复的,一定不是最优解(可以把中间的去掉)。

一共有 \(3^n-2^n\) 个状态不能达到。

A More Abstract Way/一个更抽象的方式

因为有 \(2^n\) 个状态可以达到,就可以用长度为 \(n\) 的二进制串来表示每一个状态,一共 \(2^n\) 个。我们可以尝试用 \(+1\) 来表示一步,从 \(0\) 加到 \(2^n-1\) 就做完了。

  • 如果一个 \(+1\) 不进位,就代表 \(1\) 号盘子往右移动了 \(1\) 步。

  • 反之,那个从 \(0\) 变成 \(1\) 的对应的盘子,移动到它可以移动的位置。

例如:\(3\) 个盘子。

  • \(000\rightarrow 001\)\(A\rightarrow B\)

  • \(001\rightarrow 010\)\(A\rightarrow C\)

  • \(010\rightarrow 011\)\(B\rightarrow C\)

  • \(011\rightarrow 100\)\(A\rightarrow B\)

  • \(100\rightarrow 101\)\(C\rightarrow A\)

  • \(101\rightarrow 110\)\(C\rightarrow B\)

  • \(110\rightarrow 111\)\(A\rightarrow B\)

\(X\rightarrow Y\) 代表 \(X\) 最上面的移动到 \(Z\)

为什么是对的?

一个大一点的例子: \(000000\)。第一个 \(0\) 要变成 \(1\),要经过 \(011111\rightarrow 100000\rightarrow 111111\)。对于每一位都是。也就相当于上面的盘子移开,自己移好,再把其他盘子移上来。

Change \(1\): Only Adjacent/只能相邻

现在在原问题的规定上,只能 \(A\rightarrow B,B\rightarrow C,C\rightarrow B,B\rightarrow A\)。这样,要几步呢?答案不难猜到,\(3^n-1\)

一个盘子想要移动到最终的位置上,怎么办?

  • 上面的盘子往右 \(2\) 步。

  • 自己往右 \(1\) 步。

  • 上面的盘子往左 \(2\) 步。

  • 自己往右 \(1\) 步。

  • 上面的盘子往右 \(2\) 步。

按照上面二进制的方法,这边就变成了三进制。

  • \(+1\) 不进位:\(1\) 往左/右 \(1\)

  • 反之,对应的一个盘子移动。

这种变形还有一个性质,就是一共有 \(3^n\) 个状态,它都可以达到,而且没有重复的。如果把互相可以达到的状态连一条边,从 \(AAA\) 走到 \(CCC\),会形成一个很好看的形状

(别人的图)

可以发现,原问题是最短路,这个,是最长路。

Change \(2\): Unreached Count/不能达到的状态

原问题中,如果我们最终全部放到 B,对于一个状态,怎么判断它能不能到?

Gym-100114A

在「原问题」的分析中,我们可以得知:对于盘子 \(n\),在 \(2^{n-1}\) 次移好,盘子 \(n-1\)\(2^{n-2}\)\(2^n-2^{n-2}\) 次移好……

有了这样的发现,我们可以折半倒着搜索。先贴一个表。

Number of steps Combination
0 AAA
1 BAA
2 BCA
3 CCA
4 CCB
5 ACB
6 ABB
7 BBB

这个表格里的 Combination 是 \(1\sim n\) 顺序的。

\(0\sim 3\) 中,第 \(3\) 位是 A,\(4\sim 7\) 中反之。\(0\sim 3\) 又分成了两段,是 A 和 C(我们可以再分的时候视它们为新的 A 和 B)。\(4\sim 7\) 是 C 和 B(同理)。

于是就有了这样一个思路:

  • \(n\)\(1\) 搜索。\(hanoi(cur,A,B,C)\) 为函数。

  • \(s_{cur}=C\),不可能访问到了这个状态,输出 \(\texttt{NO}\) 结束。

  • \(cur=1\)return 1

  • \(s_{cur}=A\)\(\rightarrow hanoi(cur-1,A,C,B)\)

  • \(s_{cur}=B\)\(\rightarrow hanoi(cur-1,C,B,A)\)

我们发现,这就是伪代码里的!

#include <bits/stdc++.h>

using namespace std;

#define de(x) cout<<#x<<"="<<x<<endl

using ll = long long;

int x;
string s;

int hanoi(int n,char a,char b,char c){
	if (n==1){
		if (s[0]==a || s[0]==b){
			return 1;
		}
		return 0;
	}
	else{
		if (s[n-1]==a){
			return hanoi(n-1,a,c,b);
		}
		if (s[n-1]==b){
			return hanoi(n-1,c,b,a);
		}
		return 0;
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	freopen("input.txt","r",stdin);
	freopen("output.txt","w",stdout);
	cin>>x>>s;
	if (hanoi(x,'A','B','C')){
		cout<<"YES"<<endl;
	}
	else{
		cout<<"NO"<<endl;
	}
	return 0;
}
posted @ 2023-08-23 17:35  SFlyer  阅读(35)  评论(0编辑  收藏  举报