这道题目对我来说难度比较大, 第一遍没看懂, 最近有空又看了一下, 借鉴了其他博主的方法, 终于看懂啦, 记录一下, 也分享给大家~
题目: 291. 蒙德里安的梦想 - AcWing题库https://www.acwing.com/problem/content/293/
分析方法:Acwing291. 蒙德里安的梦想:状态压缩dp_阿正的梦工坊的博客-CSDN博客https://lishizheng.blog.csdn.net/article/details/112706309代码:
(代码来源于acwing y总的题解, 根据他的讲解我自己添加了注释, 所以可能有一些错误. 也参考了上面链接里博主的代码注释)
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 12, M = 1 << N;
int n, m;//矩形的行数和列数
long long f[N][M];//第一维表示列 第二维表示状态, 二进制表示(比如00100) 大数 用longlong
bool st[M];//存储每种状态是否有奇数个连续的0,如果奇数个0是无效状态false,如果是偶数个零置为true。
int main()
{
while(cin >> n >> m, n || m)//读入棋盘的宽和长 不是0就继续
{
//把所有f置成0
memset(f, 0, sizeof f);//用memset函数给f填充为0长度是f的长度 加头文件<cstring>
//预处理1,每列不能有奇数个连续的0
//所有状态是:从0到1 左移n位 表示2^n^ ??
for(int i = 0; i < 1 << n; i++)
{
st[i] = true;//某种状态没有奇数个连续的0则标记为true
int cnt = 0;//cnt记录当前这一段连续0的个数
//遍历这一列 从上到下
for(int j = 0; j < n; j++ )
if((i >> j) & 1)//i >> j 位运算,表示i(状态)的二进制数的j位; &1为判断该位是否为1, 如果为1则进入if
{
if(cnt & 1) st[i] = false;//前面连续的0的个数是不是奇数个(cnt &1为真) 说明该状态不合法的
cnt = 0;//既然该位是1 并且经过上面if判断,该位前面不是奇数个0, 计数器可以清零.
} else cnt ++;
if(cnt & 1) st[i] = false; //判断最后一段0的个数 奇数个则false
}
//下面是dp的过程
f[0][0] = 1;//第一个0 最开始第0列什么都还没有放, 所以不会有横着的小方块伸过来 所以第二个0, 只有这一种方案
for(int i = 1; i <= m; i ++)//枚举所有的列 i 的范围是1-m
for(int j = 0; j < 1 << n; j ++)//枚举i列的所有状态j 一共有2^n^种状态 比如0000 1111..
for(int k = 0; k < 1 << n; k++)//枚举第i-1列的所有状态k 如果真正可行 就转移
if((j & k) == 0 && st[j | k])//i-1列的状态k 和 i列的状态j. 按位与, 结果等于0表示没有有冲突的行. 且状态判断为true
f[i][j] += f[i - 1][k];// 当前i列的方案数就等于第i-1列所有状态k的累加。
cout << f[m][0] << endl;
//答案就是 f[m] [0].
//即前m-1列全部摆好, 且从第m-1列到m列状态是0(即从第m-1列到第m没有伸出来的) 所有方案,即整个棋盘全部摆好的方案.
}
return 0;
}