积木画

积木画

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

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

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

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

输入格式

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

输出格式

输出一个整数表示答案。

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

数据范围

$1 \leq N \leq {10}^{7}$。

输入样例:

3

输出样例:

5

样例解释

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

 

解题思路

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

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

  转移状态如下:

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

 $$A = \begin{bmatrix}
1 & 1 & 1 & 1 \\
0 & 0 & 1 & 1 \\
0 & 1 & 0 & 1 \\
1 & 0 & 0 & 0
\end{bmatrix}$$

  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 }

  当然,也可以从第$i-1$列的状态推到第$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)} = \begin{bmatrix} f(i,0) & f(i,1) & f(i,2) & f(i,3) \end{bmatrix}$。根据上面的状态转移图以及转移矩阵就会有$$\begin{bmatrix} f(i-1,0) & f(i-1,1) & f(i-1,2) & f(i-1,3) \end{bmatrix} \times \begin{bmatrix} 1 & 1 & 1 & 1 \\ 0 & 0 & 1 & 1 \\ 0 & 1 & 0 & 1 \\ 1 & 0 & 0 & 0 \end{bmatrix} = \begin{bmatrix} f(i,0) & f(i,1) & f(i,2) & f(i,3) \end{bmatrix}$$

  即$F^{(i)} = F^{(i-1)} \times A$。而根据定义我们最终要求的答案是$F^{(n+1)}_0$,即把前$n$列填满,且第$n+1$列没有任何方块。因此通过递推就会有$$F^{(n+1)} = F^{(n)} \times A = F^{(n-1)} \times A^2 = \cdots = F^{(1)} \times A^n$$

  其中$F^{(1)} = \begin{bmatrix} 1 & 0 & 0 & 0 \end{bmatrix}$。

  AC代码如下,时间复杂度为$O(4^3 \cdot \log{n})$:

 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 @ 2022-05-22 10:51  onlyblues  阅读(159)  评论(1编辑  收藏  举报
Web Analytics