2022蓝桥杯B组—积木画——递推算法

积木画

题目描述

小明最近迷上了积木画,有这么两种类型的积木,分别为 I I I 型(大小为 2 2 2 个单位面积)和 L L L 型(大小为 3 3 3 个单位面积):

QQ截图20220410144642.png

同时,小明有一块面积大小为 2 × N 2×N 2×N 的画布,画布由 2 × N 2×N 2×N 1 × 1 1×1 1×1 区域构成。

小明需要用以上两种积木将画布拼满,他想知道总共有多少种不同的方式?

积木可以任意旋转,且画布的方向固定。

输入格式

输入一个整数 N ( 1 ≤ N ≤ 1 0 7 ) N(1\le N \le 10^7) N(1N107),表示画布大小。

输出格式

输出一个整数表示答案。

由于答案可能很大,所以输出其对 1000000007 1000000007 1000000007 取模后的值。


算法:递推 O ( n ) O(n) O(n)

思路
  • 假设没有 L L L 型积木,给你 2 × N 2\times N 2×N 的一个画布 和 I I I 型积木,那么这道题就是简单的递推题。

    • 假设 r e s [ i ] res[i] res[i] 表示在 2 × i 2\times i 2×i 的一个画布放 I I I 型积木,恰好填充完整的方案数。
      1. i = 0 i=0 i=0 时,只有一种方案:不放 → r e s [ 0 ] = 1 \rightarrow res[0]=1 res[0]=1

      2. i ≥ 0 i\ge0 i0

        • 因为我们要保证填放的积木时合法的(也就是最后能把画布刚好填充完)

        • → \rightarrow 这就意味着,我们放 I I I 型积木有两种放法: 2 ∗ 1 、 2 ∗ 2 2*1、2*2 2122

        • 在这里插入图片描述

        • 于是,对于 2 × 3 2\times 3 2×3 的画布,我们相当于:

          1. 2 × 2 2\times2 2×2 的画布基础上放置一个 2 × 1 2\times 1 2×1 的积木
          2. 2 × 1 2\times 1 2×1 的画布基础上放置一个 2 × 2 2\times 2 2×2 的积木
        • 图例:在这里插入图片描述

        • ⟶ \longrightarrow 于是我们得到了递推公式: r e s [ i ] = r e s [ i − 1 ] + r e s [ i − 2 ] res[i]=res[i-1]+res[i-2] res[i]=res[i1]+res[i2]

  • 那么回到题目,在加入 L L L 型积木后,我们怎么找到递推公式呢?

    • 这里的关键点就在于题目给定的画布为 2 × N 2\times N 2×N,并且我们的方案必定是恰好放完画布。

    • 这就意味着, L L L 型积木一定是成对出现的,不然一定无法恰好放完。

    • 图例:在这里插入图片描述

    • 于是我们进一步分析, L L L 型积木一定是成对出现的,意味着我们放 L L L 型积木的时候相当于放一个 2 × 3 2\times 3 2×3 的积木

    • 图例:在这里插入图片描述

    • 同时,翻转一下这个 2 × 3 2 \times3 2×3 的积木,也是一种方法

    • 图例:在这里插入图片描述

  • 但是这就结束了吗?

    • 仔细观测,我们会发现,虽然 L L L 型积木是成对出现的,但是我们可以在里面穿插 I I I 型积木

    • 例如:在这里插入图片描述

    • 随着穿插的 I I I 型积木数量增多,就相当于 2 × 3 、 2 × 4 、 2 × 5 ⋯ 2\times 3、2\times 4、2\times 5\cdots 2×32×42×5 的积木

  • 于是,这道题的题意就变成了:

    • 给定一个 2 × N 2\times N 2×N 的画布,以及 2 × 1 、 2 × 2 、 2 × 3 ⋯ 2\times 1、2\times 2、2\times 3\cdots 2×12×22×3 的积木,其中长度大于 3 3 3 的积木,都有两种,求恰好把画布放完的方案数。
    • 那么假设 r e s [ i ] res[i] res[i] 表示在 2 × i 2\times i 2×i 的画布上放置积木,恰好填充完整个方案的方案数。
      1. i = 0 i=0 i=0 时,只有一种方案:不放 → \rightarrow r e s [ 0 ] = 1 res[0]=1 res[0]=1
      2. i ≥ 1 i\ge 1 i1
        • 如果我们最后一步填充的是 2 × 1 、 2 × 2 2\times 1、2\times 2 2×12×2 的积木,那就相当于 r e s [ i − 1 ] 、 r e s [ i − 2 ] res[i-1]、res[i-2] res[i1]res[i2]
        • 如果我们最后一步填充的是 2 × 3 、 2 × 4 ⋯ 2\times 3、2\times 4\cdots 2×32×4 的积木,那就相当于 2 × r e s [ i − 3 ] 、 2 × r e s [ i − 4 ] 2\times res[i-3]、2\times res[i-4] 2×res[i3]2×res[i4]
        • 于是我们就得到了递推公式:
          • ⟶ r e s [ i ] = r e s [ i − 1 ] + r e s [ i − 2 ] + 2 × ( r e s [ i − 3 ] + r e s [ i − 4 ] + ⋯ + r e s [ 0 ] ) \longrightarrow res[i]=res[i-1]+res[i-2]+2\times (res[i-3]+res[i-4]+\cdots+res[0]) res[i]=res[i1]+res[i2]+2×(res[i3]+res[i4]++res[0])

代码

  • 如果直接实现递推公式,核心代码相当于两层循环 O ( n 2 ) O(n^2) O(n2)

    res[0] = 1;	// 初始化
    for(int i = 1 ; i <= n ; ++i){	// 枚举画布大小
        res[i] = res[i-1];
        if(i >= 2)	res[i] += res[i-2];
        for(int j = i-3 ; j >= 0 ; --j)	res[i] += 2*res[j]; // 枚举长度大于 3 的积木
    }
    
    • 但是由于本题的数据范围为 1 0 7 10^7 107 O ( n 2 ) O(n^2) O(n2) 的算法会导致 T L E TLE TLE
    • 于是,我们需要优化代码,我们仔细观察第二层循环,会发现其实这些状态我们在前面已经计算过了,相当于一个前缀和,于是我们能把第二层循环优化掉。
  • 真正的核心代码 O ( n 2 ) O(n^2) O(n2)

    res[0]=1;	// 初始化
    sum=0;		// 存储 0 ~ (i-3) 的前缀和
    for(int i = 1, j = -2; i <= n ; ++i, ++j){
     	res[i] = res[i-1];
        if(i >= 2)	res[i] += res[i-2];
        if(j >= 0)	sum += res[j];
        res[i] += sum << 1;
    }
    
  • 本题完整代码

    #include <iostream>
    using namespace std;
    const int N = 1e7+10, mod = 1e9+7;
    typedef long long ll;
    
    ll res[N];
    
    int main()
    {
        int n;
        cin >> n;
        
        res[0] = 1;
        ll sum = 0;
        for(int i = 1, j = -2 ; i <= n ; ++i, ++j){
            res[i] = res[i-1];
            if(i >= 2) res[i] += res[i-2], res[i] %= mod;
            if(j >= 0) sum += res[j], sum %= mod;
            res[i] += sum << 1, res[i] %= mod;
        }
        cout << res[n] << endl;
        return 0;
    }
    
  • 26
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值