POJ 2411 状压DP||组合数学

题意

1*2的牌放入N*M的网格中,问有多少种摆放方案。

题解

两种解决方案。
第一种,组合数学有一个专门的公式解决这个问题。
公式
感谢周伟大佬提供的公式,ORZ。。(同时强烈推荐读者阅读周伟的状压DP论文)

第二种,状态压缩
相比于组合数学公式,状态压缩的效率就低很多,但是由于公式不太可能短时间内推导出来,所以在比赛和做题的时候,还是状态压缩方法比较通用和便捷一些。
关于这种放牌的问题,周伟大佬称之为覆盖模型,并表明覆盖模型其实就是棋盘模型的变体。我表示完全同意。解决这个问题,依然是老办法,针对每一行,枚举每一行的状态,然后判断这一行与上一行的状态是否发生冲突,若不发生冲突则代表这一行的状态可以由上一行的状态转移而来。反复操作,直到转移到第H-1行,即可得到问题的解。
套路是死的,但是问题还是有一定的灵活性。关于这道题,最大的问题就在于冲突的判断。首先,对于第一行,需要特殊判断。第一行只能有两种摆放方式,横着放和竖着放第一个。第一行绝不可能是竖着放的第二个元素。因此,针对第一行的枚举只需要过滤这一种情况即可。假设竖着放第一个是0,其他是1。则1必须连续出现。过滤掉不连续出现1的情况即可。
对于其他行,则需要与上一行是否冲突进行判断。如果这一行是0,则上一行的这个位置只能是1。如果这一行是1,则上一行的这个位置可能是0,也可能是1。如果上一行的这个位置是1,则代表这一行是横着放的。因此下一个元素也是1,由于是横着放的,所以下一个元素的上一行不能竖着放,因此下一个元素的上一行也是1。
根据上述两个判断条件,就可以很好的完成状态转移。

注意事项

写程序的时候有点晕,犯了不少错误。首先的话,注意一下位运算的优先级吧,位运算优先级很低,所以一定要注意加括号。
第二点的话,就是注意一下(x&(1 << pos))这种运算的运算结果,这种运算可以判断某一位的值是否为0。也就是是否选择了某一位。但是一定要注意,如果不为0,并不代表这一位为1。。。

代码

#include<bits/stdc++.h>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define INF 0x3f3f3f3f
#define LL long long
#define MAXN 2100
#define EPS 1e-10
#define MOD 100000000
using namespace std;
int w,h;
LL dp[15][MAXN];

bool check(int s) {
    int pos=0;
    W(pos<w) {
        if((s&(1<<pos))==0) {
            pos++;
        } else {
            if((s&(1<<(pos+1)))==0||pos==(w-1)) {
                return false;
            }
            pos+=2;
        }
    }
    return true;
}

bool checkAB(int a,int b) {
    int pos=0;
    W(pos<w) {
        if((a&(1<<pos))==0) {
            if((b&(1<<pos))==0) {
                return false;
            } else {
                pos++;
            }
        } else {
            if((b&(1<<pos))==0) {
                pos++;
            } else if(pos==(w-1)||(b&(1<<(pos+1)))==0||(a&(1<<(pos+1)))==0) {
                return false;
            } else {
                pos+=2;
            }
        }
    }
    return true;
}


int main() {
    W(~scanf("%d%d",&h,&w)) {
        if(w+h==0)
            break;
        if(h>w)
            swap(h,w);
        memset(dp,0,sizeof(dp));
        UP(i,0,1<<w) {
            if(check(i)) {
                dp[0][i]=1;
            }
        }
        UP(k,1,h) {
            UP(i,0,1<<w) {
                UP(j,0,1<<w) {
                    if(checkAB(i,j)) {
                        dp[k][i]+=dp[k-1][j];
                    }
                }
            }
        }
        printf("%lld\n",dp[h-1][(1<<w)-1]);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值