BZOJ 1019 [汉诺塔]

题面

题意

  对于 \(n\) 层的汉诺塔,将一个盘子从一个柱子挪到另一个柱子,这样的操作一共有六种:AB,AC,BC,BA,CA,CB,对这六种操作给出优先级,每一次从所有合法的操作中选择出上一次没有移动过的盘子中操作优先级最高的操作执行,求出需要的步数。

题解

  假设空的塔底有无穷大的盘子。只考虑塔顶的盘子,每次只可能移动最小的和次小的两个盘子,而移动次小的盘子之后,次小的盘子依旧是下一时刻次小的盘子,所以最小和次小的盘子必定有一个在上一时刻被移动过,所以操作只可能是移动最小的盘子到优先级较高的堆上,以及移动次小的盘子到最大的盘子上这两个步骤的循环。

  因为优先级只对最小的盘子有影响,而最小的盘子每次操作时的开始位置都确定,所以只有从同一盘子出发的操作优先级才有意义。而根据上面步骤是循环的结论,我们已经可以模拟出最小的盘子的移动轨迹。不妨令第一个塔出发到第二个塔的优先级比到第三个塔上高(如非如此,交换第二个塔和第三个塔),这样,最小的盘子的轨迹只有三种情况:\(1,2,3,1,2,3,\dots\)\(1,2,1,2,\dots\)\(1,2,3,2,3,\dots\)

  以 \(n=4\) 为例,对于三种情况分别模拟所有盘子的移动轨迹,其中第一列表示初始状态,除去第一列的第 \(i\) 行第 \(j\) 列上有数字代表第 \(j\) 次操作时第 \(i\) 个盘子移动到了数字表示的塔上:

  • 情况一:\(1,2,3,1,2,3,\dots\)
1 2 3 1 2 3 1 2 3
1  3   2   1   3
1    2       3
1        3

  观察发现,除了最小的盘子之外需要执行 \(2^{n-1}-1\) 次操作,总共需要 \((2^{n-1}-1) \times 2+1=2^n-1\) 次操作。

  • 情况二:\(1,2,1,2,\dots\)
1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2
1  3 2   3 1   3 2   3 1   3 2   3 1   3 2   3 1   3 2
1      3     2           3     1           3     2
1                  3                 2

  同样,观察发现除了最小的盘子之外需要执行 \(3^{n-1}-1\) 次操作,总共需要 \(2 \times 3^{n-1}-1\) 次操作

  • 情况三:\(1,2,3,2,3,\dots\)
1 2 3 2 3 2 3 2 3 2 3 2 3 2 3
1  3   1 2   1 3   1 2   1 3
1    2           1     3
1          3

  这种情况下直接看出数量关系比较困难。因为最小的盘子是第一个移动的,它从 \(1\) 开始和从 \(3\) 开始没有区别,所以原序列和下方序列是等价的:

3 2 3 2 3 2 3 2 3 2 3 2 3 2 3
1  3   1 2   1 3   1 2   1 3
1    2           1     3
1          3

  而将该序列按照 \(1 \rightarrow 3,2 \rightarrow 1,3 \rightarrow 2\) 映射:

2 1 2 1 2 1 2 1 2 1 2 1 2 1 2
3  2   3 1   3 2   3 1   3 2
3    1           3     2
3          2

  发现它和情况二中的右半段是完全一致的。因此,这种情况下除了最小的盘子之外要进行 \(\frac{3^{n-1}-1}{2}\) 次操作,总共需要 \(3^{n-1}\) 次操作。
  上述观察得到的结论都可以用数学归纳法证明,但较为繁琐,此处略去证明。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll qpow(ll a,ll b){
    ll ans=1;
    while (b){
        if (b&1) ans*=a;
        a*=a; b>>=1;
    }
    return ans;
}
char s[10];
int main(){
    int i,n;
    int a[3],t;
    ll ans;
    scanf("%d",&n); memset(a,-1,sizeof(a));
    for (i=0;i<6;i++){
        scanf("%s",s);
        t=s[0]-'A';
        a[t]=(~a[t]?a[t]:s[1]-'A');
    }
    if (a[a[a[0]]]==0) ans=qpow(2,n-1)-1;
    else if (a[a[0]]==0) ans=qpow(3,n-1)-1;
    else ans=(qpow(3,n-1)-1)/2;
    printf("%lld\n",ans*2+1);
    return 0;
}
posted @ 2020-01-22 00:24  Kilo-5723  阅读(120)  评论(0)    收藏  举报