状压dp之棋盘覆盖

1.poj2411

题目大意:用2*1的骨牌覆盖满一个n*m的矩阵,求方案数。(n,m<=11)

题目分析:由于n和m都很小,可以想到状态压缩dp。如果我们f[i][j]表示某i行的状态j,在状态j中,1表示已经覆盖,0表示没有覆盖,那么有三种情况:1.不放 2.横着放 3.竖着放。用dfs来寻找每行和它上行之间可以转换的状态,上行状态是s1,这行状态是s2,l是列号,那么dfs的扩展方式如下:

1.l=l+1,s1=(s1<<1)+1,s2=(s2<<1)//因为如果这一行的这一个地方不放骨牌,那么上一行如果空着,就不符合放满骨牌的规则了。

2.l=l+2,s1=(s1<<2)+3,s2=(s2<<3)+3.

3.l=l+1,s1=(s1<<1),s2=(s2<<1)+1//因为如果这一行要竖着放,那么上一行此处必须空着

初始条件:f[0][1...111]=1//第一行不能竖着放

代码

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<queue>
#include<climits>
using namespace std;
const int INF=(1<<11);
int n,m,maxn,sum=0;//sum:对应关系的数量
int gx1[1000001],gx2[1000001];
long long f[12][INF];
void dfs(int l,int now,int last){
	if(l>m)return;
	if(l==m){sum++;gx1[sum]=now;gx2[sum]=last;return;}
	dfs(l+1,(now<<1),(last<<1)+1);//不放
	dfs(l+1,(now<<1)+1,(last<<1));//放竖着
	dfs(l+2,(now<<2)+3,(last<<2)+3);//放横着
}
int main()
{
    int i,j,k,kl,tmp1,tmp2,head,tail;
    while(1){
    	scanf("%d%d",&n,&m);
    	if(n==0&&m==0)break;
    	sum=0;
    	memset(f,0,sizeof(f));
    	if(n%2==1&&m%2==1){printf("0\n");continue;}
    	if(n>m){kl=n;n=m;m=kl;}
    	maxn=(1<<m)-1;
    	f[0][maxn]=1;//第1行不能放竖的
    	dfs(0,0,0);
    	for(i=1;i<=n;i++)
    		for(j=1;j<=sum;j++)f[i][gx1[j]]+=f[i-1][gx2[j]];
    	printf("%lld\n",f[n][maxn]);
    }
    return 0;
}

2.SGU131

题目大意:用2×1的骨牌或者2×2挖去一块的“L”形骨牌覆盖整个棋盘,求方案数

题目分析:见表格,b1表示上一行对这一行的影响,1表示影响了0表示没有,s1表示上一行状态,s2表示这一行状态。

    如果这一行要放竖着的骨牌,那么上一行此处不能放骨牌。如果上一行此处没有影响,就必须要放了骨牌才能覆盖满。

编号状态条件转移s1,s2转移b1,b2
10 0
0 0
s1=(s1<<1)+1-b1
s2=(s2<<1)+b2
b1=0
b2=0
20 0
1 1
b2=0s1=(s1<<1)+1-b1
s2=(s2<<1)
b1=0
b2=1
31 0
1 0
b1=0
b2=0
s1=(s1<<1)
s2=(s2<<1)+1
b1=0
b2=0
41 0
1 1
b1=0
b2=0
s1=(s1<<1)
s2=(s2<<1)+1
b1=0
b2=1
51 1
0 1
b1=0s1=(s1<<1)
s2=(s2<<1)+b2
b1=1
b2=1
61 1
1 0
b1=0
b2=0
s1=(s1<<1)
s2=(s2<<1)+1
b1=1
b2=0
70 1
1 1
b2=0s1=(s1<<1)+1-b1
s2=(s2<<1)+1
b1=1
b2=1

代码

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<queue>
#include<climits>
using namespace std;
const int INF=(1<<9);
long long f[10][INF];
int n,m,x;
void search(int l,int s1,int s2,int b1,int b2){
	if(l>m)return;
	if(l==m){
		if(b1==0&&b2==0){f[x][s2]+=f[x-1][s1];}
		return;}
	search(l+1,(s1<<1)+1-b1,(s2<<1)+b2,0,0);//1
	if(b1==0&&b2==0){
		search(l+1,(s1<<1),(s2<<1)+1,0,0);//3
		search(l+1,(s1<<1),(s2<<1)+1,0,1);//4
		search(l+1,(s1<<1),(s2<<1)+1,1,0);//6
	}
	if(b1==0){
		search(l+1,(s1<<1),(s2<<1)+b2,1,1);//5
	}
	if(b2==0){
		search(l+1,(s1<<1)+1-b1,(s2<<1)+1,0,1);//2
		search(l+1,(s1<<1)+1-b1,(s2<<1)+1,1,1);//7
	}
}
int main()
{
    int i,j,tmp,maxn;
    scanf("%d%d",&n,&m);
    if(n<m){tmp=n;n=m;m=tmp;}//减少递归层数
    maxn=(1<<m)-1;
    f[0][maxn]=1;
    for(x=1;x<=n;x++)//防止爆空间,不需要预存所有方案
    	search(0,0,0,0,0);
    printf("%lld",f[n][maxn]);
    return 0;
}
/*
	1: 0 0
	   0 0
	   
	2: 0 0
	   1 1
	   
	3: 1 0
	   1 0
	   
	4: 1 0
	   1 1
	   
	5: 1 1
	   0 1
	   
	6: 1 1
	   1 0
	   
	7: 0 1
	   1 1
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值