zoukankan      html  css  js  c++  java
  • [luogu p1990] 覆盖墙壁

    传送门

    覆盖墙壁

    题目描述

    你有一个长为N宽为2的墙壁,给你两种砖头:一个长2宽1,另一个是L型覆盖3个单元的砖头。如下图:

    0 0 
    0 00 
    

    砖头可以旋转,两种砖头可以无限制提供。你的任务是计算用这两种来覆盖N*2的墙壁的覆盖方法。例如一个2*3的墙可以有5种覆盖方法,如下:

    012 002 011 001 011 
    012 112 022 011 001 
    

    注意可以使用两种砖头混合起来覆盖,如2*4的墙可以这样覆盖:

    0112 
    0012 
    

    给定N,要求计算2*N的墙壁的覆盖方法。由于结果很大,所以只要求输出最后4位。例如2*13的覆盖方法为13465,只需输出3465即可。如果答案少于4位,就直接输出就可以,不用加0,如N=3,时输出5。

    输入输出格式

    输入格式

    一个整数N(1<=N<=1000000),表示墙壁的长。

    输出格式

    输出覆盖方法的最后4位,如果不足4位就输出整个答案。

    输入输出样例

    输入#1

    13
    

    输出#1

    3465
    

    分析

    此题不用多说,动态规划。但是这道题还是比较难讲的,参考了一下题解才想出来一种比较易懂的讲解?QAQ233

    (f_i)(2 imes i)尺寸墙壁的恰好覆盖方法。我们规定,(f_0 = 1)!!

    来看(n = 4)的情况⑧,现在一个都没填充:

    0000
    0000
    

    首先不考虑L形瓷砖。

    如果瓷砖的最后两格横着铺上了一形瓷砖,也就是这样:

    0001
    0001
    

    此时的方法数就是除去最后一列的子问题,也就是(f_{i-1})

    还有一种情况是瓷砖的最后四个竖着铺上了一形瓷砖,也就是这样:

    0011
    0022
    

    注意哈是横着铺。竖着铺的情况已经相当于第一种了。方法数是除去最后两列的子问题,也就是(f_{i - 2})

    答案就是两种情况的综合,也就是(f_i = f_{i - 1} + f_{i - 2})

    现在一字型瓷砖已经被我们讨论完了,再来讨论L形瓷砖。

    此时再设一个数组,设(g_i)为既要覆盖上(2 imes i)尺寸墙壁,也要恰好多出一块的覆盖方法。规定(g_0 = 0)哦!

    先在最后铺一个:

    0001
    0011
    

    然后反向思维,不看铺上的瓷砖,看空地。此时先补上前(n - 2)列的墙壁,然后再补多出的一个。也就是说,这种情况的方案数是(g_{n - 2})

    但是L形瓷砖可以反过来使用,也就是这样:

    0011
    0001
    

    此时的方案数仍然是(g_{n - 2}),所以实际上我们要统计两次(g_{n - 2})

    但是(g)数组我们怎么进行一个维护呢?首先对于突出一个方块的,我们有两种方法覆盖。

    第一种:再补一个L,补成长方形:

    0221
    0211
    

    当然

    0211
    0221
    

    也是一样的道理。

    此时的方法数显然就是(f_{n - 3})辣。

    第二种:继续补长方形,仍然多出一块

    0221
    0011
    

    虽然这看着非常难受,强迫症甚至可以为此砸掉电脑,但是这种情况完全有可能啊!(哭)

    这种情况下仍然还需要(g)数组,不过相当于又填补了一列(虽然刚刚实际上是填补了一个横向的,但是把刚刚少的那一块填上了两块,现在又多出了一块,整体还是多了一列的,自己看看图就知道了)。

    这种情况的方案数是(g_{n - 3})

    也就是说,(g_{n - 2} = f_{n - 3} + g_{n - 3}),也就等同于(g_n = f_{n - 1} + g_{n - 1})

    总结:

    [egin{cases} f_n = f_{n - 1} + f_{n - 2} + 2 imes g_{n - 2} \ g_n = f_{n - 1} + g_{n - 1} end{cases} ]

    这是两个数组的递推式结果。其实本题到这里本应该结束的,但是我们难道就不能压成一个数组了吗?

    (g_n)的递推式。

    [g_n = f_{n - 1} + g_{n - 1}\ g_n = f_{n - 1} + f_{n - 2} + g_{n - 2}\ g_n = f_{n - 1} + f_{n - 2} + f_{n - 3} + ldots + f_1 + f_0\ g_n = sum_{i = 0} ^ {n - 1}f_i = sum_{i = 1} ^ {n - 1} + 1 ]

    原式等同于:

    [f_n = f_{n - 1} + f_{n - 2} + 2 imes sum_{i = 1} ^ {n - 3}f_i + 2 ]

    然后让我施展一点魔法……让(n ightarrow n - 1)

    这样就可以得到:

    [f_{n - 1} = f_{n - 2} + f_{n - 3} + 2 imes sum_{i = 1} ^ {n - 4}f_i + 2 ]

    对比这两个式子:

    [f_n = f_{n - 1} + f_{n - 2} + 2 imes sum_{i = 1} ^ {n - 3}f_i + 2 \ f_{n - 1} = f_{n - 2} + f_{n - 3} + 2 imes sum_{i = 1} ^ {n - 4}f_i + 2 ]

    上式减下式,得到:

    [f_n - f_{n - 1} = f_{n - 1} + f_{n - 2} + 2 imes sum_{i = 1} ^ {n - 3}f_i + 2 - (f_{n - 2} + f_{n - 3} + 2 imes sum_{i = 1} ^ {n - 4}f_i + 2) ]

    将等号右边的式子整理一下,

    [f_n - f_{n - 1} = f_{n - 1} + f_{n - 3} ]

    然后把左边的(- f_{n - 1})移项,得到最终的递推式:

    [f_n = 2 imes f_{n - 1} + f_{n - 3} ]

    边界条件:

    [f_1 = 1\ f_2 = 2\ f_3 = 5 ]

    推出了式子以后,代码就很好写了。

    代码

    /*
     * @Author: crab-in-the-northeast 
     * @Date: 2020-04-14 22:11:54 
     * @Last Modified by: crab-in-the-northeast
     * @Last Modified time: 2020-04-15 15:19:02
     */
    #include <iostream>
    #include <cstdio>
    
    const int maxn = 1000005;
    const int mod = 10000;
    int dp[maxn] = {0, 1, 2, 5};
    
    int main() {
        int n;
        std :: cin >> n;
        if(n <= 3) {
            std :: cout << dp[n] << std :: endl;
            return 0;
        }
        for(int i = 4; i <= n; i++)
            dp[i] = (dp[i - 1] * 2 % mod + dp[i - 3] % mod) % mod;
        std :: cout << dp[n] << std :: endl;
        return 0;
    }
    

    评测结果

    AC 100:R32801187

  • 相关阅读:
    隐藏虚拟网卡
    Eclipse3.2编码选中对象着色
    PHP里的字符串定义小技巧汇总
    【原创】交互型网页防止IP欺骗的技巧
    VS2005的报错让我“二”了一把
    【原创】利用PHP5的__autoload代替繁琐低效的的外部文件包含方式
    关于WebDataWindow.Net的一些开发小细节
    PHP效率损失操作汇总
    动态添加按钮及关联方法(带参数)
    GridView中模版列使用RowCommand事件如何得到当前列的行索引或记录ID
  • 原文地址:https://www.cnblogs.com/crab-in-the-northeast/p/luogu-p1990.html
Copyright © 2011-2022 走看看