骨牌覆盖 状态压缩 动态规划

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_45707147/article/details/102759018

在这里插入图片描述
在这里插入图片描述
简单一点的说这个题,就是用1x2的骨牌去覆盖NxM的棋盘,有多少种方案。N, M <= 11。
初步理解
我们对N进行初步分类讨论:
当N = 1时,M为奇,无解,M为偶,有唯一解
当N = 2时,Fibonacci数列
(自己可以找例子推一下)
深入分析
可N > 2时怎么办?
由于N<=11,不难想到状态压缩,可以用二进制位运算表示。
难点:状态表示:dp[i][S]表示到达第i行,当前覆盖状态为S的方案数。
对横着放的骨牌,两个位置均置为1
对竖着放的骨牌,上方置1,下方置0

则在种情况下,我们设上一行为s1, 下一行为s2,可转移情况:
Case 1: s1某位为0,s2对应位必须为1
Case 2: s1某位为1,s2对应位可为0,可为1,但是需满足s1&s2可被1x2覆盖

满足s1&s2可被1x2覆盖 这是一个难点理解

则对于动态规划的主要过程,有

LL solve(int k, int S) {  // 到达第k行,状态为S方案数
    if (dp[k][S] != -1) return dp[k][S];
    if (k == 1) {
        if (S == two(m)-1) return dp[k][S] = 1;
        else return dp[k][S] = 0;
    }
    dp[k][S] = 0;
    for (int i = 0; i < two(m); ++i)
        if ((i|S) == two(m)-1 && Cover(i&S)) //若可以转移
            dp[k][S] += solve(k-1, i);
    return dp[k][S];
}

放代码

#include<iostream>
#include<cstring>
#define LL long long
using namespace std;
int h,w;
long long dp[15][(1<<11)+5];

bool check(int S) 
{
    for (int i=0;i<w;++i)
        if(S&(1<<i)) 
		{
            if (i==w-1||!(S&(1<<(i+1))))return false;
            ++i;
        }
    return true;
}


LL solve(int k,int s)
{
	if(dp[k][s]!=-1)	return dp[k][s];
	if(k==1)
	{
		if(s==(1<<w)-1)	return dp[k][s]=1;
		return dp[k][s]=0;
	}
	dp[k][s]=0;
	for(int i=0;i<(1<<w);i++)
		if((i|s)==((1<<w)-1) && check(i&s))
			dp[k][s]+=solve(k-1,i);
	return	dp[k][s]; 
}

int main()
{
	while(cin>>h>>w)
	{
		if(!h && !w)	return 0;
		memset(dp,-1,sizeof(dp));
		cout<<solve(h+1,(1<<w)-1)<<endl;
	}
	return 0;
}

上面是递归的写法,再来说一下递推,来先预处理出所有转移。
问题来了,如何预处理?
0和1的表示意义不变,
对于第 i 行与第 i+1 行,其中某段格子状态如下:
转移一
i: 0 i+1: 1 // 如果第 i 行的某个格子为0,那么第 i+1 行对应的一定是1才行。
转移二
i: 1 // 第 i 行的这个格子为1,有两种可能:纵向放置骨牌的下面那个;或是横向放置骨牌的第二个
i+1: 0 // 不管上面那个格子是如何放置,第i+1行都可以是0,即竖向摆放骨牌的第一个格子。
转移三
i: 1 1 i+1: 1 1 // 第 i 行和第 i+1 行都是横向排列
dfs(i,j,k)表示第i行时,上一行是j状态,下一行是k状态。
dfs(col+1, (s1<<1)|1, s2<<1);上面是1,下面是0,竖放骨牌。
dfs(col+1, s1<<1, (s2<<1)|1);上面是0,下面是1,分属于两个骨牌。
dfs(col+2, (s1<<2)|3, (s2<<2)|3);上面是11,下面是11,上面是横放骨牌。

核心代码

void dfs(int col, int s1, int s2)
{
    if (col >= m) {
        if (col == m) g[s2].push_back(s1);
        return;
    }
    dfs(col+1, (s1<<1)|1, s2<<1);
    dfs(col+1, s1<<1, (s2<<1)|1);
    dfs(col+2, (s1<<2)|3, (s2<<2)|3);
}

放代码

#include<vector>
#include<iostream> 
#include<cstring>
using namespace std;

int h,w;
vector<long long>	v[(1<<11)+5];
long long f[11+5][(1<<11)+5];

void dfs(int step,int up,int down)
{
	if(step>=w)
	{
		if(step==w)	v[down].push_back(up);
		return ;
	}	
	
	dfs(step+1,(up<<1)|1,down<<1);
	dfs(step+1,up<<1,(down<<1)|1);
	dfs(step+2,(up<<2)|3,(down<<2)|3);
}

int main()
{
	while(cin>>h>>w)
	{
		if(!h&&!w)	return 0;
		memset(f,0,sizeof(f));
		memset(v,0,sizeof(v));
		if(h<w)	swap(h,w);
		
		dfs(0,0,0);
		
		f[0][0]=1;
		for(int i=0;i<=h;i++)
			for(int j=0;j<(1<<w);j++)
				for(int k=0;k<v[j].size();k++)
					f[i+1][j]+=f[i][v[j][k]];
					
		cout<<f[h+1][(1<<w)-1]<<endl;			
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_45707147/article/details/102759018