核心:
摆放的小方格方案数
等价于 横着摆放的小方格方案数
如何判断当前方案是否合法?
遍历每一列,i
列的方案数只和i-1
列有关系
j&k==0
,i-2
列伸到i-1
的小方格 和i-1
列放置的小方格 不重复。- 每一列,所有连续着空着的小方格必须是偶数个
dp分析:
状态表示 f[i][j]
: 前i-1
列已经确定,且从第i-1
列伸出的小方格在第i
列的状态为j
的方案数。属性:个数。
i=2, j=11001
表示下列图的状态
但是i-1, i
列已经固定,所以集合划分是依据i-2 列伸到 i-1 列的不同状态 k 来划分
i-2 列伸到 i-1 列的状态 k=00100
状态计算:(限制条件:i-1
列非空白位置可以不能放置小方格),在i
列不同的放置方法就是不同的集合划分。
问题:第 i-2 列伸到 i-1 列的状态为 k , 是否能成功转移到 第 i-1 列伸到 i 列的状态为 j ?
需要满足如下条件:
j&k==0
,i-2
列伸到i-1
的小方格 和i-1
列放置的小方格 不重复。- 每一列,所有连续着空着的小方格必须是偶数个
f[m][0]
:
列数从0开始计数,m列不放小方格,前m-1列已经完全摆放好并且不伸出来的状态
#include<iostream>
#include<cstring>
using namespace std;
//数据范围1~11
const int N = 12;
//每一列的每一个空格有两种选择,放和不放,所以是2^n
const int M = 1 << N;
//方案数比较大,所以要使用long long 类型
//f[i][j]表示 i-1列的方案数已经确定,从i-1列伸出,并且第i列的状态是j的所有方案数
long long f[N][M];
//第 i-2 列伸到 i-1 列的状态为 k , 是否能成功转移到 第 i-1 列伸到 i 列的状态为 j
//st[j|k]=true 表示能成功转移
bool st[M];
//n行m列
int n, m;
int main() {
// 预处理st数组
while (cin >> n >> m, n || m) {
for (int i = 0; i < 1 << n; i++) {
// 第 i-2 列伸到 i-1 列的状态为 k ,
// 能成功转移到
// 第 i-1 列伸到 i 列的状态为 j
st[i] = true;
// 记录一列中0的个数
int cnt = 0;
for (int j = 0; j < n; j++) {
// 通过位操作,i状态下j行是否放置方格,
// 0就是不放, 1就是放
if (i >> j & 1) {
// 如果放置小方块使得连续的空白格子数成为奇数,
// 这样的状态就是不行的,
if (cnt & 1) {
st[i] = false;
break;
}
}else cnt++;
// 不放置小方格
}
if (cnt & 1) st[i] = false;
}
// 初始化状态数组f
memset(f, 0, sizeof f);
// 棋盘是从第0列开始,没有-1列,所以第0列第0行,不会有延伸出来的小方块
// 没有横着摆放的小方块,所有小方块都是竖着摆放的,这种状态记录为一种方案
f[0][0] = 1;
// 遍历每一列
for (int i = 1; i <= m; i++) {
// 枚举i列每一种状态
for (int j = 0; j < 1 << n; j++) {
// 枚举i-1列每一种状态
for (int k = 0; k < 1 << n; k++) {
// f[i-1][k] 成功转到 f[i][j]
if ((j & k) == 0 && st[j | k]) {
f[i][j] += f[i - 1][k]; //那么这种状态下它的方案数等于之前每种k状态数目的和
}
}
}
}
// 棋盘一共有0~m-1列
// f[i][j]表示 前i-1列的方案数已经确定,从i-1列伸出,并且第i列的状态是j的所有方案数
// f[m][0]表示 前m-1列的方案数已经确定,从m-1列伸出,并且第m列的状态是0的所有方案数
// 也就是m列不放小方格,前m-1列已经完全摆放好并且不伸出来的状态
cout << f[m][0] << endl;
}
return 0;
}
orz orz 感谢大佬让我渡劫
我还是有点看不懂
自己的一些理解:
https://www.acwing.com/solution/content/239291/
还得有图才更清楚
确实
第一张图是哪个课里的啊?没见过
算法基础课
为什么f[0][0]为1,,我是这样想的,既然没有从不存在的-1列伸出,那么只考虑竖着放在第一列,,那么不是应该行数为奇数时为0,偶数时为0嘛???求解答
是通过第 i - 1 列状态a与第 i 列的状态b共同去判断第 i - 1 列是否可以合法摆放,可以的话第 i 列的这个状态b才能加上第i-1列的a的方案数,所以dp[0][0]其实仅仅是一个初始状态,该状态并不关心本身是否合法,合不合法由之后去判定,合法的话只能加1,所以给他置为1
因为特殊情况不能用常规方法通式处理,只能初始化特殊处理,后面常规处理
i >> j & 1 请问这个什么意思鸭?
判断二进制数字i的第j位是否为1 为1则对应于第j行放木块的情况 不为1则不放。我觉得应该是这样
i是这一列的状态,i >> j 是表示列上的某一格,(i >> j) &1表示这一格是否被占用,(i >> j) &1如果为1表示被占用,为0表示这一格是空的
好厉害,我小白都看懂了
妙啊妙啊松鼠爱死你了
感谢大佬让我眼前一亮
这里不是很理解,有无大佬解释一下,f[i][j] += f[i - 1][k]; //那么这种状态下它的方案数等于之前每种k状态数目的和
那个j&k和st[j|k]到底是怎么计算的,我知道那个要不重复和必须是偶数个的含义,但转换成代码具体具体时怎么判断的,搜了与和或运算也还是不是很明白
与相当于交集,或相当于并集,相与要求为0是在要求i-2到i-1和i-1到i,在公共的i-1列不相交。或是获取在i-1列上的状态,然后判断是否合法。
tql
摆放的小方格方案数 等价于 横着摆放的小方格方案数
这是为啥?
横着的摆完且合法,那剩下的位置只能放竖着的,相当于横的放完,竖的方法就被限死了,只能嵌进去
松鼠厉害呀
i < 1 << n这个是什么意思啊?
1 << n的结果与2的n次方的结果一样
哥,if ((j & k) == 0 && st[j | k])这个表示什么意思
j & k,判断(i-2)列放的横方块和(i-1)列放的横方块有没有重叠
好的,感谢哥
Orz
能不能把预处理提到while循环外面,直接一步到位呢
不能直接提到外面,举个例子:同样是
i == 0
,行数N是奇数
,st[0] = false
,反之如果是偶数
,st[0] = true
不愧是大神,向大神顶礼膜拜
写的很好!
松鼠yyds