您的位置:首页 > 其它

POJ 2411 状压DP||组合数学

2017-07-31 15:40 183 查看

题意

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]);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: