积木画

积木画

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

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

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

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

输入格式

输入一个整数 N,表示画布大小。

输出格式

输出一个整数表示答案。

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

数据范围

1N107

输入样例:

3

输出样例:

5

样例解释

五种情况如下图所示,颜色只是为了标识不同的积木:

 

解题思路

  这题用状压dp来做,f(i,j)表示已经操作完前i1列(前i1列的方格已经被积木全部填满),且第i列的状态为j的所有方案的集合。可以发现j一共又22=4种状态,我们用00来表示两个方格都没有积木;01表示下面的方格有,上面的方格没有;10表示下面的方格没有,上面的方格有;11表示两个方格都有积木。

  我们同时规定,对于横跨两列的积木,以这个积木最左边的方块所在的列为它进行操作的列。

  转移状态如下:

  用一个4×4的矩阵来表示,如果aij=1,表示可以从当前列的i状态转移到下一列的j状态,aij=0则表示无法转移,根据上图,得到的状态转移矩阵就是

 A=[1111001101011000]

  AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 1e7 + 10, mod = 1e9 + 7;
 5 
 6 int g[4][4] = {
 7   {1, 1, 1, 1},
 8   {0, 0, 1, 1},
 9   {0, 1, 0, 1},
10   {1, 0, 0, 0}
11 };
12 int f[N][4];
13 
14 int main() {
15     int n;
16     cin >> n;
17     
18     f[1][0] = 1;    // 一开始还没有放任何积木,即第1列的状态为0
19     for (int i = 1; i <= n; i++) {
20         for (int j = 0; j < 4; j++) {       // 枚举第i行的状态
21             for (int k = 0; k < 4; k++) {   // 枚举可以转移到的第i+1行的状态
22                 if (g[j][k]) {              // 如果可以从第i行的j状态转移到第i+1行的k状态
23                     f[i + 1][k] = (f[i + 1][k] + f[i][j]) % mod;    // 转移
24                 }
25             }
26         }
27     }
28     
29     cout << f[n + 1][0];    // 表示前n行已经填满,且第n+1行的状态为0
30     
31     return 0;
32 }
复制代码

  可以用滚动数组来优化空间,把f数组第一维的大小从N优化到2,AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 1e7 + 10, mod = 1e9 + 7;
 5 
 6 int g[4][4] = {
 7   {1, 1, 1, 1},
 8   {0, 0, 1, 1},
 9   {0, 1, 0, 1},
10   {1, 0, 0, 0}
11 };
12 int f[2][4];
13 
14 int main() {
15     int n;
16     cin >> n;
17     
18     f[1][0] = 1;
19     for (int i = 1; i <= n; i++) {
20         memset(f[i + 1 & 1], 0, sizeof(f[i + 1 & 1]));
21         for (int j = 0; j < 4; j++) {
22             for (int k = 0; k < 4; k++) {
23                 if (g[j][k]) {
24                     f[i + 1 & 1][k] = (f[i + 1 & 1][k] + f[i & 1][j]) % mod;
25                 }
26             }
27         }
28     }
29     
30     cout << f[n + 1 & 1][0];
31     
32     return 0;
33 }
复制代码

  当然,也可以从第i1列的状态推到第i列的状态,AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 1e7 + 10, mod = 1e9 + 7;
 5 
 6 int f[2][4];
 7 
 8 int main() {
 9     int n;
10     scanf("%d", &n);
11     
12     f[1][0] = 1;
13     for (int i = 2; i <= n + 1; i++) {
14         memset(f[i & 1], 0, sizeof(f[i & 1]));
15         f[i & 1][0] = (f[i - 1 & 1][0] + f[i - 1 & 1][3]) % mod;
16         f[i & 1][1] = (f[i - 1 & 1][0] + f[i - 1 & 1][2]) % mod;
17         f[i & 1][2] = (f[i - 1 & 1][0] + f[i - 1 & 1][1]) % mod;
18         for (int j = 0; j <= 2; j++) {
19             f[i & 1][3] = (f[i & 1][3] + f[i - 1 & 1][j]) % mod;
20         }
21     }
22     
23     printf("%d", f[n + 1 & 1][0]);
24     
25     return 0;
26 }
复制代码

  更新一种用矩阵乘法加快速幂的做法。

  状态的定义与上面的一样,其中这里再定义F(i)=[f(i,0)f(i,1)f(i,2)f(i,3)]。根据上面的状态转移图以及转移矩阵就会有[f(i1,0)f(i1,1)f(i1,2)f(i1,3)]×[1111001101011000]=[f(i,0)f(i,1)f(i,2)f(i,3)]

  即F(i)=F(i1)×A。而根据定义我们最终要求的答案是F0(n+1),即把前n列填满,且第n+1列没有任何方块。因此通过递推就会有F(n+1)=F(n)×A=F(n1)×A2==F(1)×An

  其中F(1)=[1000]

  AC代码如下,时间复杂度为O(43logn)

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int mod = 1e9 + 7;
 5 
 6 int a[4][4] = {
 7     {1, 1, 1, 1},
 8     {0, 0, 1, 1},
 9     {0, 1, 0, 1},
10     {1, 0, 0, 0}
11 };
12 int tmp[4][4];
13 
14 void mul(int c[][4], int a[][4], int b[][4]) {
15     memset(tmp, 0, sizeof(tmp));
16     for (int i = 0; i < 4; i++) {
17         for (int j = 0; j < 4; j++) {
18             for (int k = 0; k < 4; k++) {
19                 tmp[i][j] = (tmp[i][j] + 1ll * a[i][k] * b[k][j]) % mod;
20             }
21         }
22     }
23     memcpy(c, tmp, sizeof(tmp));
24 }
25 
26 int main() {
27     int n;
28     scanf("%d", &n);
29     int f[4][4] = {1};
30     int k = n;
31     while (k) {
32         if (k & 1) mul(f, f, a);
33         mul(a, a, a);
34         k >>= 1;
35     }
36     printf("%d", f[0][0]);
37     
38     return 0;
39 }
复制代码

 

参考资料

  AcWing 4406. 积木画(蓝桥杯C++ AB组辅导课):https://www.acwing.com/video/3799/

posted @   onlyblues  阅读(168)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示