[BZOJ] 1019 [SHOI2008]汉诺塔
题目链接:[SHOI2008]汉诺塔
题意:
给定玩汉诺塔的策略:
1.上一次移动过的盘不能再次被移动。
2.三个柱子,给定每两个柱子之间移动的优先级,每次必须执行优先级最高的可行操作。
题解:
其实移动方法是被固定的,然后可以发现一些性质。
根据汉诺塔的一般解题思路,我们肯定是要首先移动第一根柱子最上面的$n-1$个盘到另一个柱子上去。
那么我们就要思考,在这个过程中,最底下的盘会动吗?
答案是不会,考虑起始、中间和末尾的移动过程。
起始的时候肯定是移动最上面那个盘,到优先级最高的地方去。
中间的时候,要么三个柱子是满的,要么最底下的盘上面还有盘压着,否则的话肯定有一个柱子被空出来了,而如果此时最底下的盘没东西压着,肯定已经移动结束了。
末尾的时候,最后一步肯定是把最小的盘从一个柱子移动到目标柱子的顶部,于是它不能动,此时,能移动的只有原本柱子上最大的盘,且只能移动到唯一一个空的柱子上去。
那么就有解题思路了。
显然当我们要处理某一堆盘的时候,比这一堆大的盘都是被锁死的,动不了,我们可以记搜这个方案。
$f[i][x]$表示把第$i$个柱子上$x$个盘子移动到另一个柱子需要多少步。
$g[i][x]$表示把第$i$个柱子上$x$个盘子移动到的是哪一个柱子。
假如$x=1$,我们就要按照优先级来,移动一步。
否则的话,我们先移动上面一堆盘,再移动最下面那个盘到空柱子上去。
接下来我们要移动上面一堆盘,但是此时就会有问题,因为有优先级在,所以上面一堆盘不一定会移动到最大的那个盘的顶部,所以我们需要循环移动,直到两个东西重合。
显然这个循环次数不会很多,因为如果循环很多都移动不上去的话,看起来就像是一个死循环,但是题目保证有解,所以放心地循环就可以了。
1 #include <bits/stdc++.h> 2 #define int long long 3 #define pii pair<int, int> 4 #define Mid ((l + r) / 2) 5 #define lson (rt << 1) 6 #define rson (rt << 1 | 1) 7 using namespace std; 8 int read() { 9 char c; int num, f = 1; 10 while(c = getchar(),!isdigit(c)) if(c == '-') f = -1; num = c - '0'; 11 while(c = getchar(), isdigit(c)) num = num * 10 + c - '0'; 12 return f * num; 13 } 14 int n, f[4][39], g[4][39]; 15 pii a[10]; 16 void dfs(int x, int cnt) { 17 if(f[x][cnt] != -1) return ; 18 f[x][cnt] = 0; 19 if(cnt == 1) { 20 f[x][cnt] = 1; 21 for(int i = 1; i <= 6; i++) { 22 if(a[i].first == x) { 23 g[x][cnt] = a[i].second; 24 break; 25 } 26 } 27 return ; 28 } 29 dfs(x, cnt - 1); 30 int nowup = g[x][cnt - 1], nowdown = x; 31 f[x][cnt] += f[x][cnt - 1]; 32 do{ 33 nowdown = 6 - nowdown - nowup; 34 f[x][cnt] += 1; 35 dfs(nowup, cnt - 1); 36 f[x][cnt] += f[nowup][cnt - 1]; 37 nowup = g[nowup][cnt - 1]; 38 } while(nowup != nowdown); 39 g[x][cnt] = nowdown; 40 return ; 41 } 42 signed main() 43 { 44 n = read(); 45 memset(f, -1, sizeof(f)); 46 for(int i = 1; i <= 6; i++) { 47 char c[10]; 48 scanf("%s", c + 1); 49 a[i].first = c[1] - 'A' + 1; 50 a[i].second = c[2] - 'A' + 1; 51 } 52 dfs(1, n); 53 printf("%lld\n", f[1][n]); 54 return 0; 55 }