• 题面描述

    • 汉诺塔由三根柱子(分别用\(A\ B\ C\)表示)和\(n\)个大小互不相同的空心盘子组成。一开始\(n\)个盘子都摞在柱子\(A\)上,大的在下面,小的在上面,形成了一个塔状的锥形体。

      对汉诺塔的一次合法的操作是指:从一根柱子的最上层拿一个盘子放到另一根柱子的最上层,同时要保证被移动的盘子一定放在比它更大的盘子上面(如果移动到空柱子上就不需要满足这个要求)。我们可以用两个字母来描述一次操作:第一个字母代表起始柱子,第二个字母代表目标柱子。例如,\(AB\)就是把柱子\(A\)最上面的那个盘子移到柱子\(B\)。汉诺塔的游戏目标是将所有的盘子从柱子\(A\)移动到柱子\(B\)或柱子\(C\)上面。有一种非常简洁而经典的策略可以帮助我们完成这个游戏。首先,在任何操作执行之前,我们以任意的次序为六种操作\((AB,AC,BA,BC,CA,CB)\)赋予不同的优先级,然后,我们总是选择符合以下两个条件的操作来移动盘子,直到所有的盘子都从柱子\(A\)移动到另一根柱子:

      • 这种操作是所有合法操作中优先级最高的;

      • 这种操作所要移动的盘子不是上一次操作所移动的那个盘子。

      可以证明,上述策略一定能完成汉诺塔游戏。现在你的任务就是假设给定了每种操作的优先级,计算按照上述策略操作汉诺塔移动所需要的步骤数。

  • 输入格式

    • 输入有两行。第一行为一个整数\(n\ (1\leq n\leq 30)\),代表盘子的个数。第二行是一串大写的\(ABC\)字符,代表六种操作的优先级,靠前的操作具有较高的优先级。每种操作都由一个空格隔开。
  • 输出格式

    • 只需输出一个数,这个数表示移动的次数,保证答案\(\leq 10^{18}\)
  • 题解

    • 首先当所有可能的操作都被赋予优先级,并且限制相邻两次操作不能移同一个对象,因此该汉诺塔的移动序列已经被固定。

    • 我们要做的就从最优化变成了合并操作序列中相同的部分,加速计算效率

    • 类比普通的汉诺塔问题,不难发现在移盘子的时候总会不时出现将\(1,.., x\)个盘子以同一个操作序列从某个地方移到某个地方。

    • 令三根柱子分别叫\(x,y,z\)

    • 是这个启发,我们可以设计出如下状态:

      • \(f[i][x][y]\)表示\(1,..,i\)个盘子从\(x\)移到\(y\)所需的步数
      • 特别的,当\(f[i][x][y]==-1\)时表示\(1,..,i\)个盘子从\(x\)移到\(y\)不合法
    • 这样我们可以写出转移方程

      • \[f_{i,x,y}=f_{i-1,x,y}+1+f_{i-1,y,x}+1+f_{i-1,x,y}\\
        或\\
        f_{i,x,y}=f_{i-1,x,z}+1+f_{i-1,z,y}
        \]

      • 注意到当\(1,..,i-1\)个盘子没有移完的时候,我们是不可能去移第\(i\)个盘子的

        • 分两种情况

          • 一,还有一些盘子在第\(i\)个盘子上,那我们不可能移动第\(i\)个盘子
          • 二,第\(i\)个盘子上已经没有盘子了,又分两种情况
            • \(1,..,i-1\)个盘子在同一根柱子,那么这时候我们称这\(i-1\)个盘子已经移好了,与假设不符
            • \(1,..,i-1\)个盘子不在同一根柱子,那么我们不能移第\(i\)个盘子,因为如果能够移动第\(i\)个盘子,移动到的柱子上已没有比\(i\)小的盘子,但此时两根柱子都有\(1,..,i-1\)中的某一些盘子
      • 也就是说,我们必须把\(1,..,i-1\)个盘子移好再移第\(i\)个盘子

      • 故第一个递推方程表示

        • 当\(1,..,i-1\)个盘子从\(x\)移到\(y\)合法,且\(1,..,i-1\)个盘子从\(y\)移到\(x\)合法时,

          • 我们先将\(1,..,i-1\)个盘子从\(x\)移到\(y\),花费\(f_{i-1,x,y}\)步。
          • 因为相邻两次操作对象不能相同,我们只能将第\(i\)个盘子从\(x\)移到\(z\),花费\(1​\)步。
          • 因为相邻两次操作对象不能相同,我们只能移\(1,..,i-1\)盘子,同时我们不能把这\(1,..,i-1\)盘子移到第\(i\)个盘子所在的\(z\)柱子,因为如此一来,我们在下一步又只能移\(1,..,i-1\)盘子,这是不合法的。因此我们只能把\(1,..,i-1\)盘子移到\(x\)柱子,花费\(f_{i-1,y,x}\)步
          • 因为相邻两次操作对象不能相同,我们只能将第\(i\)个盘子从\(x\)移到\(y\),花费\(1\)步。
          • 最后我们将\(1,..,i-1\)移回\(y\),花费\(f_{i-1,x,y}\)步
      • 第二个递推方程表示

        • 当\(1,..,i-1\)盘子从\(x\)移到\(z\)合法,且\(1,..,i-1\)盘子从\(z\)移到\(y\);合法时,

          • 我们将\(1,..,i-1\)盘子从\(x\)移到\(z\),花费\(f_{i-1,x,z}\)步
          • 因为相邻两次操作对象不能相同,我们只能将第\(i\)个盘子从\(x\)移到\(y\),花费\(1\)步。
          • 最后我们将\(1,..,i-1\)盘子从\(z\)移回\(y\),花费\(f_{i-1,z,y}\)步
    • 同时因为操作的优先级,我们不难发现这两个递推是不可能同时成立,但有可能同时不成立。因此当这两个递推式同时不成立时\(f_{i,x,y}=-1\)

    • 关于边界条件,根据题目给出的操作优先级确定

      • 例如:从\(x\)向其他柱子移动的操作优先级为\(xy>xz\),因此\(f_{1,x,y}=1,f_{1,x,z}=-1\)
    • 如此递推即可

    • 网上好多博客一本正经地说了一个奇怪的\(dp\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=55;
typedef long long ll;
ll f[MAXN][4][4];
int n;
char s[10][4];
int main(){
scanf("%d",&n);
for (int i=1;i<=6;i++) scanf("%s",s[i]);
for (int i=0;i<3;i++){
f[1][i][0]=f[1][i][1]=f[1][i][2]=-1;
for (int j=1;j<=6;j++){
if (s[j][0]-'A'==i){
int to=s[j][1]-'A';
f[1][i][to]=1;
break;
}
}
}
for (int i=2;i<=n;i++){
for (int j=0;j<3;j++){
for (int k=0;k<3;k++){
int l=3-j-k;
f[i][j][k]=-1;
if (f[i-1][j][k]!=-1&&f[i-1][k][j]!=-1){
f[i][j][k]=f[i-1][j][k]*2+2+f[i-1][k][j];
}
if (f[i-1][j][l]!=-1&&f[i-1][l][k]!=-1){
f[i][j][k]=f[i-1][j][l]+1+f[i-1][l][k];
}
// cout<<i<<" "<<j<<" "<<k<<" "<<f[i][j][k]<<endl;
}
}
}
if (f[n][0][1]!=-1) printf("%lld\n",f[n][0][1]);
else printf("%lld\n",f[n][0][2]);
return 0;
}

随机推荐

  1. QtCreator下QML翻译

    首先打开.pro工程文件,在文件中添加文本段:TRANSLATIONS = testTranslate_zh.ts 在pro右键,单击再次弹出命令提示,如图 在命令行中,输入lupdate testT ...

  2. 使用#include消除重复代码

    消除重复代码代码很多种,比如: 1)提炼成函数复用 2)使用宏 3)继承 4)使用闭包(boost::bind.boost::function) 上述是最为常用的,对于C++程序,闭包可能用得相对少一 ...

  3. mysql 数据库查询

    myspl   三部分  文件  服务  界面 查看数据库命令   show databases 创建数据库  create database  库名 删除数据库   drop database   ...

  4. Python 数据分析—第十章 日期处理

    日期时间数据类型及工具 from datetime import datetime now = datetime.now() print(now.year,now.month,now.day) #以毫 ...

  5. Windows GDI绘图基础知识

    一.Windows可以画直线.椭圆线(椭圆圆周上的曲线)和贝塞尔曲线.////////////7 个画线函式是:(1)画直线LineTo    BOOL LineTo(HDC hdc,int nXEn ...

  6. MIDA Converter Basic patched for RAD Studio 10.1.2 Berlin (VCL转换到FMX)

    Mida is the only way to try to convert your project from VCL to FireMonkey. Version after version, M ...

  7. Thrift框架学习

    参考文章:1.http://www.kankanews.com/ICkengine/archives/54084.shtml 2.http://www.cnblogs.com/liping135991 ...

  8. 解决centOS 本地可以访问 外部主机不能访问的问题

    但是centos中的防火墙规则比较严密 用curl http://localhost:10000 可以看到内容,但是外部无法访问,原因是防火墙没有开启10000端口,需要将10000端口加入到信任规则 ...

  9. SQL Server分页查询存储过程

    --分页存储过程create PROCEDURE [dbo].[commonPagination]@columns varchar(500), --要显示的列名,用逗号隔开 @tableName va ...

  10. [C#]合并单元格(行、列)

    详细链接:https://shop499704308.taobao.com/?spm=a1z38n.10677092.card.11.594c1debsAGeak説明:控件ID指的是页面上面的Grid ...