zoukankan      html  css  js  c++  java
  • poj2411 Mondriaan's Dream[状压dp]

    Mondriaan's Dream
    Time Limit: 3000MS   Memory Limit: 65536K
    Total Submissions: 16265   Accepted: 9413

    Description

    Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, after producing the drawings in his 'toilet series' (where he had to use his toilet paper to draw on, for all of his paper was filled with squares and rectangles), he dreamt of filling a large rectangle with small rectangles of width 2 and height 1 in varying ways. 

    Expert as he was in this material, he saw at a glance that he'll need a computer to calculate the number of ways to fill the large rectangle whose dimensions were integer values, as well. Help him, so that his dream won't turn into a nightmare!

    Input

    The input contains several test cases. Each test case is made up of two integer numbers: the height h and the width w of the large rectangle. Input is terminated by h=w=0. Otherwise, 1<=h,w<=11.

    Output

    For each test case, output the number of different ways the given rectangle can be filled with small rectangles of size 2 times 1. Assume the given large rectangle is oriented, i.e. count symmetrical tilings multiple times.

    Sample Input

    1 2
    1 3
    1 4
    2 2
    2 3
    2 4
    2 11
    4 11
    0 0
    

    Sample Output

    1
    0
    1
    2
    3
    5
    144
    51205
    

    Source

    状态压缩DP

    经典覆盖问题,输入n和m表示一个n*m的矩形,用1*2的方块进行覆盖,不能重叠,不能越出矩形边界,问完全覆盖完整个矩形有多少种不同的方案

    其中n和m均为奇数的话,矩形面积就是奇数,可知是不可能完全覆盖的。接着我们来看n*m为偶数的情况

    DP前先处理一下,交换n和m使n较大m较小,这样能减少状态数

    另外数据中是有重复的,所以开辟一个ans数组来记录每组数据的结果,如果遇到相同的数据则不要计算直接输出答案

    不用这个ans数组的话也不会超时,这个代码是跑出了950ms,加了这个记录答案的数组时间变为600ms

    接着就看注释部分的讲解即可


    最上面的为第1行,最下面为第n行
    从上到下按行DP
    其中一行的状态我们用一个二进制表示,0表示没有被覆盖,1表示被覆盖了
    最后得到一个01串,这个串变回十进制就是一个状态
    定义状态dp[i][s],表示前i-1行已经放满,第i行的状态为s的方案数
    状态转移方程为 dp[i][s]=sum{ dp[i-1][ss] } ,其中状态s与状态ss兼容
    这个状态转移方程的内涵在于理解s和ss何为兼容
    首先我们约定一个放置方法,就是竖着放的时候,我们暂且将其称为“上凸型摆放”
    因为竖放必然占据第i-1行和第i行,我们约定这个方块是属于第i行的,也就是说它凸上去了
    那么要在第i行的第j列竖放一个方块的话,第i-1行第j列必须没有方块
    也就是说,第i行的放置是受到第i-1行的限制的,反过来说在第i行竖放了方块,也会影响第i-1行的状态
    所以这样就可以讲解一下状态转移方程了,前i-2行已经放满了,第i-1行的状态为ss(dp[i-1][ss])
    此时在第i行开始放一些方块,放的方法不定,可能横放可能竖放,但是按这个方案放完后
    第i-1行刚好被填满,且第i行的状态变为了s,所以不难想到第i-1行的状态ss到第i行的状态s这个转移是唯一的
    所以有 dp[i][s]=sum{ dp[i-1][ss] }
    最后我们详细讨论一下s和ss在什么情况下是兼容的
    1.第i行的第j列为1,第i-1行的第j列为1,这样的话,说明第i行的第j列一定不是竖放而是横放否则会与第i-1行的第j列冲突
    所以马上紧接着判断第i行第j+1列,如果是1,那么满足横放的规则,同时也要第i-1行第j+1列也要为1,否则的话这个格子没办法填充,
    成立后向左移动两格
    不满足上述条件的,就是两个不兼容或者不合法的状态
    2.第i行第j列为1,第i-1行第j列为0,那么说明第i行第j列应该竖放并填充第i-1行第j列,成立后向左移动一格
    3.第i行第j列为0,说明不放方块,那么第i-1行第j列必须为1,否则没法填充这个格子。若第i-1行第j列也为0,不兼容不合法
    (至于第i行第j列这个格子空着干什么,其实就是留出来给第i+1行竖放的时候插进来的)

    那么目标状态是什么,就是dp[n][maxs],maxs表示全部是1的串,即第n-1行以上全部覆盖满,第n行的状态为maxs,即没有空着的格子,也全部覆盖满了
    即整个矩形全部被覆盖满了的状态

    最后是第1行的初始化问题,因为约定了“上凸型摆放”,所以第1行是不能竖放方格的,只能横放方格,
    每横放一个必定占据两个格子,所以在判断一个状态(那个01串)的时候,连着的1的个数必定为偶数,如果出现了单独的1,说明不合法

                        转载自http://www.cnblogs.com/scau20110726/archive/2013/03/14/2960448.html

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=12;
    int n,m;long long ans[N][N],f[N][1<<N];
    bool init(int s){
        for(int j=0;j<m;j++){
            if(s&(1<<j)){
                if(j==m-1) return 0;
                if(s&(1<<j+1)) j++;
                else return 0;
            }
        }
        return 1;
    }
    bool can(int s,int ss){
        for(int j=0;j<m;j++){
            if(s&(1<<j)){
                if(ss&(1<<j)){
                    if(j==m-1||!(s&(1<<j+1))||!(ss&(1<<j+1))) return 0;
                    else j++;
                }
            }
            else{
                if(ss&(1<<j)) continue;
                else return 0;
            }
        }
        return 1;
    }
    void dp(){
        memset(f,0,sizeof f);
        if(n<m) swap(n,m);
        int maxS=(1<<m)-1;
        for(int S=0;S<=maxS;S++) if(init(S)) f[1][S]=1;
        for(int i=2;i<=n;i++){
            for(int S=0;S<=maxS;S++){
                for(int SS=0;SS<=maxS;SS++){
                    if(can(S,SS)){
                        f[i][S]+=f[i-1][SS];
                    }
                }
            }
        }
        printf("%lld
    ",f[n][maxS]);
        ans[n][m]=ans[m][n]=f[n][maxS];
    }
    int main(){
        memset(ans,-1,sizeof ans);
        while(~scanf("%d%d",&n,&m)){
            if(!n&&!m) break;
            if((n&1)&(m&1)){puts("0");continue;}
            if(~ans[n][m]){printf("%lld
    ",ans[n][m]);continue;}
            dp();
        }
        return 0;
    }
  • 相关阅读:
    SQL必知必会-笔记(五)函数
    软件测试面试题:系统中的图片不显示如何排查原因
    windows用浏览器访问linux目录文件
    记测试工作中一次印象深刻的事
    怎么快速适应新的测试工作?
    xshell如何导出日志文件和上传文件
    jmeter+fiddler高效率整理接口脚本
    python-用requests库处理form-data格式的参数
    软件自动化测试工程师面试题集锦(4)
    shell脚本批量检查某个或多个服务的端口和进程是否正常
  • 原文地址:https://www.cnblogs.com/shenben/p/6628755.html
Copyright © 2011-2022 走看看