BZOJ1019

传送门:BZOJ1019

没有写二维的Dp,理由是不优美。
然后我硬掰出来的递推更不优美。

直接说结论吧,以下讨论均设操作序列给定且恒定。
f(n) 为完成初始盘数为n的游戏所需步数,则 f(n) 满足线性递推式

f(n)=k×f(n1)+b

这个结论的证明是比较复杂的。

首先注意 n2 的情况是平凡的,我们讨论 n3

引理1:

汉诺塔的构造总是递归的

这是显然的

推论1:

g(n)1,2 表示将n个盘子从1号柱移到2号柱所需时间,类似地定义 g(n)1,3,g(n)2,3...
则有

f(n)=g(n1)1,2+b+g(n1)2,3

f(n)=g(n1)1,3+b+g(n1)3,2

恒成立,其中b表示把1柱上最大的圆盘移走的操作数,它显然是个常数。

根据引理1,这依然是显然的。

定理:

g1,2 函数和 g1,3 函数有明显的线性关系,类似的, g2,3 函数和 g3,4 函数有明显的线性关系。

这是因为:由引理1, g1,3 函数可被写作: g1,2+b+g2,3
类似的,其余g函数也有如此纷杂的关系,考虑边界后,我们就能导出这个线性的证明。
于是根据数学归纳法就可以导出我们想要证明的线性递推式。

注意:上定理是非常直观的,但其数学证明不是严格的,因水平原因,笔者至今没有找到其严格证明,如果有谁能够给出其严格证明,请联系笔者。


于是这道题的算法就明显了,我们枚举 f(3),f(4),f(5) ,解出 k,b ,然后利用一阶递推式求解。

代码上的小细节见下。

/**************************************************************
    Problem: 1019
    User: Jerusalem
    Language: C++
    Result: Accepted
    Time:0 ms
    Memory:1280 kb
****************************************************************/


#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <stack>
using namespace std;

struct Node{
    int from,to;
};

Node can[7];
stack<int> tot[4];
int n;

void Readdata()
{
//  freopen("loli.in","r",stdin);
    scanf("%d\n",&n);
    for(int i=1;i<=6;i++){
        char a,b;
        scanf("%c%c",&a,&b);
        can[i].from=a-'A'+1;
        can[i].to=b-'A'+1;
        scanf("%c",&a);
    }
}

int Try(int w)
{
    int ans=0;
    for(int i=1;i<=3;i++)
        while(!tot[i].empty())
            tot[i].pop();
    for(int i=w;i>=1;i--)
        tot[1].push(i);
    int old=-1;
    while(tot[3].size()<w&&tot[2].size()<w){
        for(int i=1;i<=6;i++)
            if(!tot[can[i].from].empty()&&tot[can[i].from].top()!=old&&(tot[can[i].to].empty()||tot[can[i].to].top()>tot[can[i].from].top())){
                tot[can[i].to].push(old=tot[can[i].from].top());
                tot[can[i].from].pop();
                ans++;
                break;
            }
        //printf("%d %d %d %d\n",ans,tot[1].size(),tot[2].size(),tot[3].size());
    }
    return ans;
}

void Solve()
{
    if(n<=3)
        printf("%d\n",Try(n));
    else{
        int a=(Try(3)-Try(2))/Try(2)+1;
        int b=Try(3)-a*Try(2);
        long long f[10005];
        f[3]=Try(3);
        for(int i=4;i<=n;i++)
            f[i]=a*f[i-1]+b;
        cout<<f[n]<<endl;
    }
}

void Close()
{
    fclose(stdin);
    fclose(stdout);
}

int main()
{
    Readdata();
    Solve();
    Close();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值