链接:http://acm.hust.edu.cn/vjudge/problem/34699分析:考虑编号最大的盘子,如果这个盘子的初始局面和目标局面中都是位于同一根柱子上,那么根本不需要移动它,因为没有其它的盘子编号比它大,说明它所在的这根柱子中它在最底层,也不会影响其它盘子的移动。这样,我们可以在初始局面和目标局面中,找出所在柱子不同的盘子中编号最大的一个,设为k,那么k必须移动。我们可以设想移动k之间的一瞬间柱子上的情况,假设盘子k要从柱子1移到柱子2,由于编号比k大的盘子都放好了不需要移动,而且不会碍事,可以直接无视。编号比k小的盘子不能在柱子1上(每次只能移动1个盘子),也不能在柱子2上(编号大的不能压在编号小的盘子上),因此只能在柱子3上,换句话说,此时柱子1中只有k,柱子2为空,柱子3从上到下依次是盘子1,2,3,...,k-1(忽略比编号大于k的盘子),我们把这个局面称为参考局面。由于盘子的移动是可逆的,根据对称性,我们只需要求出初始局面和目标局面移动成参考局面的步数之和,然后加1(移动盘子k)即可(有点双向广度优先搜索的味道)。总结一下我们需要写一个函数f(P, i, final),表示已知各个盘子的初始柱子编号数组为P(P[i]代表盘子i的柱子编号),把盘子(1,2,3,...,i)全部移动到柱子final上所需的步数。则本题的答案就是f(start,k-1,6-start[k]-finish[k])+f(finish,k-1,6-start[k]-finish[k])+1。start[i]和finish[i]是本题输入中盘子i的初始柱子和目标柱子,k为上述必须移动的编号最大的盘子的编号。我们把盘子编号为1,2,3,所以除了柱子x和柱子y之外的那个柱子编号为6-x-y。如何计算f(P[i],i,final)?当P[i]=final时,说明第i个盘子已经正确的放在第final号柱子上不需要移动,接下来只需要移动i-1号盘子到final柱子上,那么f(P,i,final)=f(P,i-1,final)。否则需要先把前i-1个盘子挪到6-P[i]-final这个柱子上做中转,然后把第i个盘子移动柱子final,最后把钱i-1个盘子从中转的柱子移动到目标柱子final上,那么此时移动第i个盘子的一瞬间,需要把第i个盘子移到final(1次)上,然后把前i-1个盘子从6-P[i]-final这根柱子上移到第final根柱子上(根据汉诺塔问题的经典结论这个步骤需要2^(i-1)-1)步,加起来总共需要2^(i-1)步,换句话说,当P[i]不等于final时,f(P,i,final)=f(P,i-1,6-P[i]-final)+2^(i-1),递归边界是i==0。
1 #include <cstdio> 2 3 typedef long long LL; 4 const int maxn = 60 + 5; 5 6 int n, start[maxn], finish[maxn]; 7 8 LL f(int* P, int i, int final) { 9 if (i == 0) return 0; 10 if (P[i] == final) return f(P, i - 1, final); 11 return f(P, i - 1, 6 - P[i] - final) + (1LL << (i - 1)); 12 } 13 14 int main() { 15 int kase = 0; 16 while (scanf("%d", &n) == 1 && n) { 17 for (int i = 1; i <= n; i++) scanf("%d", &start[i]); 18 for (int i = 1; i <= n; i++) scanf("%d", &finish[i]); 19 int k = n; 20 while (k >= 1 && start[k] == finish[k]) k--; 21 LL ans = 0; 22 if (k >= 1) { 23 int other = 6 - start[k] - finish[k]; 24 ans = f(start, k - 1, other) + f(finish, k - 1, other) + 1; 25 } 26 printf("Case %d: %lld\n", ++kase, ans); 27 } 28 return 0; 29 }
时间: 2024-10-22 22:15:02