poj2411 Mondriaan‘s Dream状压DP

题目大意

给定高度和宽度的一个棋盘,现在要用高度为1,宽度为2的长方形覆盖这个棋盘,问方案数有多少

问题分析

  • 初学状压DP,现在已经知道这道题是状压DP的问题了,关键在于如何状压呢?考虑到状压DP原理就是将状态压缩成二进制数,那么这个高度为1,宽度为2的长方形只有两种摆放方式,一种是横着放,另一种是竖着放,这样,可以规定,横着放的长方形两个格子里面都写1,竖着放的两个格子上面写0,下面写1,那么也就实现了状态压缩
  • 因为一个格子只有两种状态,一种是0,另一种是1,所以如果说一行有 m m m个格子,那么这一行的二进制状态一共就有 [ 0 , 2 m − 1 ] [0,2^m-1] [0,2m1]个总共 2 m 2^m 2m个,所以考虑枚举这所有的状态排除掉不合法的状态,这也就是先看自身的不合法,这只有一种情况那就是一行里面出现连续的1的次数为奇数,也就是该状态对应二进制数有连续奇数个1,判断程序如下
bool check1(int st){
    int bit = 0;
    while(st){
        if(st & 1) bit++;
        else{
            if(bit & 1) return false;
            else bit = 0;
        }
        st >>= 1;
    }
    if(bit & 1) return false;
    return true;
}
  • 接下来需要考虑的是当前行和上一行之间是否冲突,通过这个考虑,可以看出这道题是要根据上一行来推断下一行的,也就是说具有最优子结构性质,印证了动态规划的合理性。如果说一行有 m m m个数字,当前行和上一行要么都是1,要么一个0,一个1,不可能都是0,所以两行的状态取或结果必须为 2 m 2^m 2m,否则不合法;还有一种情况,如果出现 011 111 011\\111 011111这种情况,这其实是合法的,但是如果是这样 0111 1111 0111\\1111 01111111那就不合法,怎么来判断这种情况呢?这里有一种非常巧妙的方法,如果设第一行状态为 s t 1 st1 st1,第二行状态为 s t 2 st2 st2,取 s t 1 & s t 2 st1\&st2 st1&st2,只要这个合法就合法了,这个判断程序如下
bool check2(int st1, int st2, int total){
    if((st1 | st2) != total - 1) return false;
    return ok[st1 & st2];
}
  • 接下来开始状态转移,因为当前行可以通过上一行的状态转移过来,所以如果设 d p [ i ] [ s t ] dp[i][st] dp[i][st]表示第 i i i行状态为 s t st st的方案数,那么它可以从上一行状态为st1的 d p [ i − 1 ] [ s t 1 ] dp[i-1][st1] dp[i1][st1]转移过来,也就是 d p [ i ] [ s t ] = d p [ i ] [ s t ] + d p [ i − 1 ] [ s t 1 ] dp[i][st]=dp[i][st]+dp[i-1][st1] dp[i][st]=dp[i][st]+dp[i1][st1] d p dp dp入口处,也就是第一行的不同状态对应的方案数显然应该为1,预先处理所有的合法方案,完整程序如下
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
int Data[MAXN];
ll dp[15][1 << 11];
int ok[1 << 11];
bool check1(int st){
    int bit = 0;
    while(st){
        if(st & 1) bit++;
        else{
            if(bit & 1) return false;
            else bit = 0;
        }
        st >>= 1;
    }
    if(bit & 1) return false;
    return true;
}
bool check2(int st1, int st2, int total){
    if((st1 | st2) != total - 1) return false;
    return ok[st1 & st2];
}
int main(){
    int n, m;
    int total = (1 << 11);
    int num = 0;
    for(int i=0;i<total;i++){
        if(check1(i)) ok[i] = 1;
    }
    while(cin >> n >> m){
        if(n == 0 && m == 0) break;
        memset(dp, 0, sizeof dp);
        total = (1 << m);
        for(int i=0;i<total;i++){
            if(ok[i]) dp[0][i] = 1;
        }
        for(int i=1;i<n;i++){
            for(int j=0;j<total;j++){
                for(int k=0;k<total;k++){
                    if(!check2(j, k, total)) continue;
                    dp[i][j] += dp[i-1][k];
                }
            }
        }
        cout << dp[n-1][total - 1] << "\n";
    }
    return 0;
}
六、DP的优化技巧 6.1 预处理合法态 很多问题中,大部分态是不合法的,可以预先筛选: cpp vector valid_states; for (int state = 0; state < (1 << n); ++state) { if (check(state)) { // 检查state是否合法 valid_states.push_back(state); } } 6.2 滚动数组优化 当态只依赖前一个阶段时,可以节省空间: cpp vector<vector> dp(2, vector(size)); // 只保留当前和上一个态 int now = 0, prev = 1; for (int i = 1; i <= n; ++i) { swap(now, prev); for (auto& state : valid_states) { dp[now][state] = 0; // 清空当前态 // 态转移… } } 6.3 记忆化搜索实现 有时递归形式更直观: cpp int memo[1<<20][20]; // 记忆化数组 int dfs(int state, int u) { if (memo[state][u] != -1) return memo[state][u]; // 递归处理… return memo[state][u] = res; } 七、常见问题与调试技巧 7.1 常见错误 位运算优先级:总是加括号,如(state & (1 << i)) 数组越界:态数是2ⁿ,不是n 初始态设置错误:比如TSP中dp[1][0] = 0 边界条件处理不当:如全选态是(1<<n)-1,不是1<<n 7.2 调试建议 打印中间态:将二进制态转换为可视化的形式 cpp void printState(int state, int n) { for (int i = n-1; i >= 0; --i) cout << ((state >> i) & 1); cout << endl; } 从小规模测试用例开始(如n=3,4) 使用assert检查关键假设 八、学习路线建议 初级阶段: 练习基本位操作 解决简单问题(如LeetCode 464、526题) 中级阶段: 掌握经典模型(TSP、棋盘覆盖) 学习优化技巧(预处理、滚动数组) 高级阶段: 处理高维(如需要同时缩多个态) 结合其他算法(如BFS、双指针) 九、实战练习题目推荐 入门题: LeetCode 78. Subsets(理解态表示) LeetCode 464. Can I Win(简单DP) 中等题: LeetCode 526. Beautiful Arrangement LeetCode 691. Stickers to Spell Word 经典题: POJ 2411. Mondriaan’s Dream(棋盘覆盖) HDU 3001. Travelling(三进制) 挑战题: Codeforces 8C. Looking for Order Topcoder SRM 556 Div1 1000. LeftRightDigitsGame2 记住,掌握DP的关键在于: 彻底理解二进制态表示 熟练运用位运算 通过大量练习培养直觉 希望这份超详细的教程能帮助你彻底掌握DP!如果还有任何不明白的地方,可以针对具体问题继续深入探讨。 请帮我转成markdown语法输出,谢谢
08-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值