蒙德里安的梦想(状压DP),原题+详细过程+注释

原题链接291. 蒙德里安的梦想 - AcWing题库

求把 N×M 的棋盘分割成若干个 1×2 的长方形,有多少种方案。

例如当 N=2,M=4时,共有 5 种方案。当 N=2,M=3时,共有 3 种方案。

如下图所示:

2411_1.jpg

输入格式

输入包含多组测试用例。

每组测试用例占一行,包含两个整数 N 和 M。

当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。

输出格式

每个测试用例输出一个结果,每个结果占一行。

数据范围

1≤N,M≤11

输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205

 思路:首先从横向的小长方形入手,先把横向的小长方形放置完,并且保证

①剩余的空位还能放置纵向的小长方形

②横向的小长方形不会重叠

如下图,我们发现以下情况有些位置不能再放置纵向的小方块,所以要实现①,要保证每一列出现的连续空位都为偶数,这样才能保证所有空位都能放下纵向的小方块。

        

 为了枚举某一列可能出现所有情况,我们把大长方形的长的所有二进制表示一一枚举表示所有情况;

例如,一个4x4的大长方形,每一列可能出现的状况有(1表示有横向小长方形,0表示没有)

0000 0001 0010 0011   

0100 0101 0110 0111

1000 1001 1010 1011

1100 1101 1110 1111

当情况为0001时,发现0连续出现3次,连续空位为奇数,不能刚好放下纵向小长方形,排除

当情况为0011时,发现0连续出现2次,连续空位为偶数,可以刚好放下纵向小长方形

满足了①条件,第一列的小长方形和第二列的小长方形重合了怎么办?

如下图

还需要把相邻两列可能发生的情况再进行一次枚举,假设第i列情况k1为1100,第i+1列情况k2为0011,则不会有重合,通过k1&k2来判断是否有重合来排除小长方形重合时的情况。

最终代码

 

#include<bits/stdc++.h>
using namespace std;
const int N=12,M=(1<<N);
typedef long long ll;
ll f[N][M];
int a,b;
bool st[M];
int main()
{
	while(cin>>a>>b,a||b)
	{
		for(int i=0;i<(1<<a);i++)
		{
			int cnt=0;				
			//0未放置的方块连续出现的次数,如果为偶数则能放下纵向小长方形 
			
			st[i]=true;
			for(int j=0;j<a;j++)
			//j用来枚举每一种情况的每一位 
			{
				if((i>>j)&1)
				//i的第j位已经放置了长方形,统计上面出现的空位置出现的次数 
				{
					if(cnt&1)
					//若空位置出现的次数为奇数,此情况不可行 
					{
						st[i]=false;
						break;
					}
					else cnt=0;//若为偶数出现次数归0,这行可以忽略 
				}
				else cnt++;//如果该位是0统计次数加一 
			}
			if(cnt&1)st[i]=false;
		}
		memset(f,0,sizeof(f));   
		f[0][0]=1;
		for(int i=1;i<=b;i++)//从第一列枚举到最后一列 
		{
			for(int j=0;j<(1<<a);j++)//这是第i列 
			{
				for(int k=0;k<(1<<a);k++)//这是第i-1列 
				{
					if((j&k)==0&&st[j|k])
				//(j&k)判断是否有重合
				//(j|k)是i-1列插入了第i列之后第i列的情况,再用st数组判断是否可行 
					{
						f[i][j]+=f[i-1][k];//状态转移 
					}
				}
			}
		}
		cout<<f[b][0]<<endl;
		/*
		f[b]代表第b-1列已经伸出来到b列,
		并且第b列还没开始往外伸的时候(伸出来就超过大长方形了)
		*/
	}
}

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值