Mondriaan's Dream POJ2411 - 基础状压

本文介绍了一个经典的矩形填充问题,即如何使用2×1的小矩形填充任意大小的大矩形。文章详细解析了解决方案,并通过状态压缩动态规划的方法实现了高效计算。
Mondriaan's Dream
Time Limit: 3000MS Memory Limit: 65536K

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






这一题很做了好久。。。。。。。。

主要是一开始状态没设计好


我们先看这种情况

我们用0表示横着的和竖着的上半部分,用1表示竖着的下半部分

总而言之,如果为1,就表示是上一行的竖着的方块占用下来的


所以状态就出来了,用 f [ i ] [ S ] 表示到第 i 行的时候,状态为 S (如果有1,那就是从上一行竖着的下来的),并且前面 i-1 行已经填满的情况

所以转移也就出来了  f [ i ] [ S ] = ∑ f [ i - 1 ] [ st ]

最后的答案就是 f [ n + 1 ] [ 0 ] 


剩下的就是判断 S 和 st 是否为可行状态了

看这个例子

st 0 1 0 0 
S  1 1 1 0

我们前面已经说了,如果有1的话,那么必定是上一行下来的,所以上一行的这个位置就肯定是0!所有在第二个位置就不合法了,怎么快速判断呢?与运算~

既然当前行为1的话,上一行就必定为0,也就是不可能有两个1同时出现,所以 (S&st)==0 的话就表示满足了上述条件


剩下的就是看能否用横着的木块把剩下的填满了

那是不是直接判断 S 就行了呢?看下面的例子

st 1 0 0 0 
S  0 0 0 0 

如果判断 S 的话,显然可以用两个横着的填满,但我们仔细观察,虽然 S 可行,但是 st 就明显不可行了

如果 S 用两个横着的,st 用一个横着的,就还剩一个无法配对,而这一切都是因为 st 中有一个是从 st 的上一行传下来的,这就影响了 S 的判断,所以判断的时候还必须要同时考虑到 S 和 st !

怎么解决呢?把他们两个或起来就ok了,S|st=1000,这样就会返回false

再看个例子

st 0 0 0
S  1 0 0

S|st=100,就会返回true,一眼可以看出是对的

那这样会出错吗?比如下面的例子

st 1 0 0
S  0 0 0

我们我们如果不用或运算,直接看S,显然是无法用横着的填完的。。。。。

但事实也正并非如此,S 可以一个横着,然后一个竖着到下一行,也可以三个竖着到下一行

所以用或运算就简单多了,排除了不少麻烦        ------>        S|st=100 ,会返回true


好了,到此也就完了,不懂得就看代码,才刚开始写这种题,代码不是很凝练,但是加了很多注释,思路很清晰

    /*http://blog.youkuaiyun.com/jiangzh7 
    By Jiangzh*/  
    #include<cstdio>  
    #include<cstring>  
    typedef long long LL;  
    const int N=13;  
    int n,m,all;  
    LL f[N][1<<N];  
      
    void PT(int s)//_Debug用的,打印01序列  
    {  
        int a[N];  
        for(int i=m;i>0;i--)  
        {  
            a[i]=s&1;  
            s>>=1;  
        }  
        for(int i=1;i<=m;i++) printf("%d",a[i]);  
        puts("");  
    }  
      
    bool hash(int s)//判断剩下的0能否用横着的填满  
    {  
        for(int i=0;i<m;)  
        {  
            if((s&(1<<i))==0)  
            {  
                if(i==m-1) return false;//如果第m-1位为0,但是已经是最后一位,显然无法满足了  
                if ((s&(1<<(i+1)))!=0) return false;//当前为0,但是下一位不为0,显然不成立  
                i+=2;  
            }  
            else i++;  
        }  
        return true;  
    }  
      
    bool could(int s1,int s2)  
    {  
        bool flag1=(s1&s2)==0;//可行的话两个1必定不会重叠位置  
        bool flag2=hash(s1|s2);//看剩下的位置能否用横着的木板填满  
        return flag1&&flag2;  
    }  
      
    void work()  
    {  
        all=(1<<m)-1;  
        f[1][0]=1;  
        for(int i=1;i<=n;i++)  
        for(int S=0;S<=all;S++)  
        for(int st=0;st<=all;st++)  
        {  
            //PT(S);PT(st);  
            if(could(S,st))  
            {  
                //printf("%d\n",f[i+1][S]);  
                f[i+1][S]+=f[i][st];  
            }  
        }  
        printf("%lld\n",f[n+1][0]);//Linux下要用lld,Windows下用I64d,可以统一用cout的,难得改了  
    }  
      
    int main()  
    {  
        freopen("poj2411.in","r",stdin);  
        freopen("poj2411.out","w",stdout);  
        while(scanf("%d%d",&n,&m)==2 && n && m)  
        {  
            memset(f,0,sizeof(f));  
            work();  
        }  
        return 0;  
    }  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值