积木画
积木画
小明最近迷上了积木画,有这么两种类型的积木,分别为 型(大小为 个单位面积)和 型(大小为 个单位面积):
同时,小明有一块面积大小为 的画布,画布由 个 区域构成。
小明需要用以上两种积木将画布拼满,他想知道总共有多少种不同的方式?
积木可以任意旋转,且画布的方向固定。
输入格式
输入一个整数 ,表示画布大小。
输出格式
输出一个整数表示答案。
由于答案可能很大,所以输出其对 取模后的值。
数据范围
。
输入样例:
3
输出样例:
5
样例解释
五种情况如下图所示,颜色只是为了标识不同的积木:

解题思路
这题用状压dp来做,表示已经操作完前列(前列的方格已经被积木全部填满),且第列的状态为的所有方案的集合。可以发现一共又种状态,我们用来表示两个方格都没有积木;表示下面的方格有,上面的方格没有;表示下面的方格没有,上面的方格有;表示两个方格都有积木。
我们同时规定,对于横跨两列的积木,以这个积木最左边的方块所在的列为它进行操作的列。
转移状态如下:
用一个的矩阵来表示,如果,表示可以从当前列的状态转移到下一列的状态,则表示无法转移,根据上图,得到的状态转移矩阵就是
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 }
可以用滚动数组来优化空间,把数组第一维的大小从优化到,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 }
当然,也可以从第列的状态推到第列的状态,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 }
更新一种用矩阵乘法加快速幂的做法。
状态的定义与上面的一样,其中这里再定义。根据上面的状态转移图以及转移矩阵就会有
即。而根据定义我们最终要求的答案是,即把前列填满,且第列没有任何方块。因此通过递推就会有
其中。
AC代码如下,时间复杂度为:
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/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16297192.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效