nyoj 515 完全覆盖 II(状压dp)

 

完全覆盖 II

时间限制:1000 ms  |  内存限制:65535 KB

难度:4

描述

有一天acmj在玩一种游戏----用2*1或1*2的骨牌把m*n的棋盘完全覆盖。但他感觉把棋盘完全覆盖有点简单,他想能不能把完全覆盖的种数求出来?由于游戏难度增加他自己已经没法解决了,于是他希望大家能用程序来帮他把问题解决了。

输入

有多组数据。
每组数据占一行,有两个正整数n(0<n<12),m(0<m<12)。
当n,m等于0时输入结束

输出

每组数据输出占一行,输出完全覆盖的种数。

样例输入

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

样例输出

2
3
5
144
51205

 

 

 

 

#include <stdio.h>
#include <string.h>
typedef long long ll;
int n, m;
ll a[15][15];
ll dp[15][1<<12]; // dp[i][j] 表示第i行取状态j的情况数(1-i行均不冲突)

//  1表示横着放 0表示竖着放 
bool ok1( int s ) // 行 1不冲突 0冲突
{
    int i;  
    for(i=0;i<n;) {  
        if(s & (1<<i)) { // 如果第i格横着放
            // 如果这个已经是最后一个了 就不能横着放了
            if(i==n-1) return 0;  
            // 如果这个和下一个都是横着放 则继续判断
            else if(s & (1<<(i+1))) i+=2;  
            // 否则就是后一个不为0  则不符合横着放的规则(连续两个1)
            else return 0;  
        }  
        // 如果是竖着放就留给ok2判断 此时先认为成立
        else i++;  
    }  
    // 无冲突
    return 1;  
}

// 这里1是横着放 0是竖着放 
// 但为了防止一种不易解释的情况 当前位置出现了一个0
// 无法区分是和上一行合并还是和下一行合并 因此有时会使用一个1来占位 
// 所以当上一行是1 这一行不是1 则说明上一行的1 其实是竖着放的0
// 当上一行是0 这一行就需要用1 来占位 表示这个竖着放需要和下一行合并
bool ok2( int s, int ss ) // 上一行状态a和这一行状态b不冲突 1: 不冲突 0: 冲突
{
    int i;  
    for(i=0;i<n;) {  
        if(s & (1<<i)) { // 如果上一行该位置为1 横着放
            // 这一行的状态和上一行无关
            if(ss & (1<<i)) { // 这一行该位置横着放 且下一个位置处上下两行都不能是竖着放
                if(i==n-1 ||!(s &(1<<i+1))||!(ss &(1<<i+1))) return 0;  
                else i+=2;  
            }  
            // 上一行虽然是1 但是这一行这里不是1  说明这里行上一行的1联合竖着放
            else i++;  
        }  
        else { // 上一行该位置是竖着放
            // 这里对应位置使用了一个1占位 防止0分不清是和上一行对应还是下一行对应
            if (ss & (1<<i)) i++;  
            else return 0;  
        }  
    }  
    return 1; 
}
 
void solve()
{
    int i, s, ss;
    if ( m < n ) {
        int t;
        t=n;n=m;m=t;
    }
    // 根据集合的整数表示[https://blog.csdn.net/hopyGreat/article/details/80992740]
    // 0-maxx标记了一行的所有可能表示情况
    int maxx = (1<<n)-1;
    memset( dp, 0, sizeof(dp) );
    for(i=0;i<=maxx;i++)
    {
        // 取出一个状态判断该行是否发生冲突
        // 如果不冲突设置dp[1][i]的值为1
        if ( ok1(i) )
            dp[1][i] = 1;
    }
    
    for(i=2;i<=m;i++) { // 遍历每一行
        for(s=0;s<=maxx;s++) { // i-1行的每种状态
            for(ss=0;ss<=maxx;ss++) { // i行的每种状态 
                if (ok2(s,ss)) { //判断第i-1行与第i行情况是否兼容
                    // 如果不冲突 则第i行状态为ss的有这么多种情况
                    dp[i][ss]+=dp[i-1][s];  
                }  
            }  
        }  
        // 保存i行n列的结果数
        a[i][n] = a[n][i] = dp[i][maxx];
    }
//  a[m][n] = a[n][m] = dp[m][maxx];
    printf( "%lld\n", a[m][n] );
}
 
int main()
{
    memset( a,-1,sizeof(a) );
    while(scanf("%d%d",&m,&n)!=EOF&&n&&m)
    {
        if ( a[m][n] != -1 ) {
            printf( "%lld\n", a[m][n] );
            continue;
        }
        if ( m&1 && n&1 ) {
            a[m][n] = a[n][m] = 0;
            printf( "%d\n", a[m][n] );
            continue;
        }
        solve();
    }
 
    return 0;
}

 

 

 

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值