其他分享
首页 > 其他分享> > 积木画

积木画

作者:互联网

积木画

小明最近迷上了积木画,有这么两种类型的积木,分别为 $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$则表示无法转移,根据上图,得到的状态转移矩阵就是

\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 }

 

参考资料

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

标签:10,状态,int,积木,画布,方格
来源: https://www.cnblogs.com/onlyblues/p/16297192.html