poj 2411 Mondriaan's Dream

状态压缩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,说明不合法
*/

#include <cstdio>
#include <cstring>
#define N 15
#define MAX (1<<11)+10

long long dp[N][MAX];
long long ans[N][N];
int n,m;

bool init(int s)
{
    for(int k=0; k<m; )
    {
        if(s & (1<<k))
        {
            if(k==m-1) return false;
            if(s&(1<<(k+1))) k+=2;
            else return false;
        }
        else k++;
    }
    return true;
}

bool ok(int s, int ss)
{
    for(int j=0; j<m; )
        if(s & (1<<j)) //第i行第j列为1
        {
            if( ss & (1<<j)) //第i-1行第j列也为1,那么第i行必然是横放
            {
                //第i行和第i-1行的第j+1都必须是1,否则是非法的
                if( j==m-1 || !(s&1<<(j+1)) || !(ss&(1<<(j+1))) ) return false;
                else  j+=2;
            }
            else j++; //第i-1行第j列为0,说明第i行第j列是竖放
        }
        else //第i行第j列为0,那么第i-1行的第j列应该是已经填充了的
        {
            if(ss&(1<<j)) j++;//已经填充
            else return false;
        }
    
    return true;
}

void solve()
{
    int maxs;
    if(n<m)
    { n=n^m; m=n^m; n=n^m; }
    //交换后n是行m是列,m较小,那么状态数也可以相应减少
    maxs=(1<<m)-1;
    memset(dp,0,sizeof(dp));
    
    for(int s=0; s<=maxs; s++) //枚举第一行所有可能的状态
        if(init(s))
        {
            dp[1][s]=1; //方案数都是1
            //printf("%d\n",s);
        }

    for(int c=2; c<=n; c++) //按行dp
        for(int s=0; s<=maxs; s++) //第i行的状态
            for(int ss=0; ss<=maxs; ss++) //第i-1行的状态
                if(ok(s,ss))
                    dp[c][s] += dp[c-1][ss];
    
    
    printf("%lld\n",ans[n][m]=ans[m][n]=dp[n][maxs]);
}

int main()
{
    memset(ans,-1,sizeof(ans));
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(!n && !m) break;
        if(!ans[n][m]) 
        {
            printf("%lld\n",ans[n][m]);
            continue;
        }
        if(n&1 && m&1) 
        {
            ans[n][m]=ans[m][n]=0;
            printf("0\n");
            continue;
        }
        solve();
    }
    return 0;
}

 

 

posted @ 2013-03-14 22:08  Titanium  阅读(3965)  评论(0编辑  收藏  举报