状态压缩DP->蒙德里安的梦想(Acwing)

题意:n*m的平面,问有多少种方式能被1 * 2的小条子放满。n, m <= 11。

分析:状压DP。由上一行转到当前行。 定义状态竖着放的上半部分为1,可知上一行&当前行为0,并且上一行|当前行不含奇数长度0。剪枝 + 交换n,m降复杂度。

没想到如何表示状态跟转移
定义竖着木头的上半部分为1,其他为0。 当前行从上一行转移,那么j & k == 0是一个条件。 还有一个条件是j | k,剩下为0的格子没有出现奇数长度的格子。(剩下为0的格子要横着放)
定义好状态后,枚举所有可能的状态,把有奇数长度格子的状态记录下来。(好像不预处理,直接在计算的时候去求也可以,求完记录一下就行)

枚举有奇数长度的空格时,全为0时状态为false。
初始状态dp[0][0] = 1,唯一合法的状态,第0行什么都不放。
然后从1~n转移即可。
m > n可以进行交换。

*/

void solve(){
int n, m;

while (cin >> n >> m && n && m){
if (n * m % 2){
cout << "0\n";
continue;
}
if (n < m){
swap(n, m);
}
vector has_odd(1 << m);
for (int i = 0; i < (1 << m); ++i){
int cnt = 0;
int cur = 0;
for (int j = 0; j < m; ++j){
if ((i >> j) & 1){
cnt |= cur;
cur = 0;
}
else{
cur ^= 1;
}
}
has_odd[i] = (cur | cnt);
}

vector<vector> dp(2, vector (1 << m));
int cur = 1;
dp[0][0] = 1;
for (int i = 1; i <= n; ++i){
fill(dp[cur].begin(), dp[cur].end(), 0);
for (int j = 0; j < (1 << m); ++j){
for (int k = 0; k < (1 << m); ++k){
if ((j & k) == 0 && has_odd[j | k] == false){
dp[cur][j] += dp[cur ^ 1][k];
}
}
}
cur ^= 1;
}

cout << dp[cur ^ 1][0] << '\n';
}
}

posted @ 2024-01-16 10:02  _Yxc  阅读(56)  评论(0)    收藏  举报