状态压缩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<bool> 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<long long>> dp(2, vector<long long> (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';
}
}

浙公网安备 33010602011771号