[DP]Hanoi

题目描述

Mpq 小时候只玩过俄罗斯方块这个经典的小游戏,当时他还不知道Hanoi 究竟是
什么东西。话说当Mpq 第一次认识Hanoi 是在初三那年的联赛。由于Mpq 之前并不知
道Hanoi 是什么东西,所以那一年他做完前三题之后很郁闷地坐了1 个半小时。。。
好了,现在Mpq 成长了,他已经解决当年联赛那道Hanoi 了,在前几个月,他又
发现一道关于Hanoi 的题目了,很幸运的是这个题目他知道怎么做了。。。然后为了让大
家体验一下Mpq 初三联赛那种无奈的感觉,所以,这道题就神奇地出现在你们眼前。
Task:赶快AC 这道题目,然后你就可以狂鄙视,甚至是无视Mpq 的存在了!!!
哎,吹着吹着发现我还没把题目写下来。。。。
现在给你M 根柱子,初始的时候有N 个大小不一样的盘插在第一根柱子上面。同
样地,规格大的盘子不能放在规格比它小的盘子上面。问最少需要多少次的移动才能将
这N 个盘从第一根柱子移动到最后一根柱子上面?

Input

输入文件的第一行有两个整数n,m(1≤n≤100000,3≤m≤10),分别表示有n 个盘子和m
根柱子。

Output

输出文件只有一行,一个整数,表示最少的移动次数。保证这个移动次数不会超过
2^63-1。

Sample Input

4 3

Sample Output

15

Data Constraint

Hint

数据约定:
对于30%的数据,M=3
对于80%的数据,1≤N≤100,3≤M≤10
对于100%的数据,1≤N≤100000,6≤M≤10

分析

容易想到fi,j表i个盘子,j个柱子时的最优解
然后我们想到最优解是这样的:
上面一部分跳出来,自己组成一个塔,剩下的再移到最后一个塔,最后刚开始的再拆分,放到塔上
那么我们把眼光放在上面跳出来的那部分,因为均摊出来再摆上的值很容易求(前提是你要知道上面跳的那些)
想到用数组gi,j表示i个盘子,j个柱子的最优解时,上面移出了多少个盘子
考虑状态转移
当你要多加一个盘子时,有两种情况:
一个是:这个盘子是和上面一起跳出来的,那么很明显gi,j=gi-1,j +1
还有:这个盘子和下面留在一起,明显:gi,j=gi-1,j
怎么确定呢?
先确定状态转移方程:
fi,j=fk,j+fk,j+fi-k,j-1
(k表上面的gi,j)
前面第一个fk,j表示把上面的挪出来所需的最少步数
第二个fk,j表示先挪出来的结尾再放回去的最少步数(一样的)
第三个fi-k,j-1表示下面的移到结尾的最少步数,为什么是j-1?因为前面挪出来那部分占了一个塔
然后看看是gi,j=gi-1,j的情况优还是gi,j=gi-1,j +1的情况优,决定gi,j的值即可

#include <iostream>
#include <cstdio>
#define rep(i,a,b) for (i=a;i<=b;i++)
using namespace std;
int n,m;
long long f[100001][11],g[100001][11];
int i,j,k;
int main()
{
    freopen("hanoi.in","r",stdin);
    freopen("hanoi.out","w",stdout);
    scanf("%d%d",&n,&m);
    rep(i,1,63)
    {
        f[i][3]=f[i-1][3]*2+1;
        g[i][3]=i-1;
    }
    rep(i,1,n)
    rep(j,4,m)
    {
        f[i][j]=9223372036854775807;
        k=g[i-1][j];
        if (f[i][j]>f[k][j]*2+f[i-k][j-1])
        {
            f[i][j]=f[k][j]*2+f[i-k][j-1];
            g[i][j]=k;
        }
        k++;
        if (f[i][j]>f[k][j]*2+f[i-k][j-1])
        {
            f[i][j]=f[k][j]*2+f[i-k][j-1];
            g[i][j]=k;
        }
    }
    printf("%lld\n",f[n][m]);
}

猜你喜欢

转载自blog.csdn.net/ssl_qyh0ice/article/details/80029530