题解 | 《算法竞赛进阶指南》 蒙德里安的梦想

本文介绍了一种利用状压动态规划方法解决矩形填充问题的算法,该问题要求计算用1×2大小的砖块填充N×M大小矩形的所有可能方案数量。通过枚举每行所有可能的状态并进行状态转移,最终求得解决方案,适用于数据范围较小的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目】

Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, after producing the drawings in his ‘toilet series’ (where he had to use his toilet paper to draw on, for all of his paper was filled with squares and rectangles), he dreamt of filling a large rectangle with small rectangles of width 2 and height 1 in varying ways. Expert as he was in this material, he saw at a glance that he’ll need a computer to calculate the number of ways to fill the large rectangle whose dimensions were integer values, as well. Help him, so that his dream won’t turn into a nightmare!

【题意】

即给定一个N×MN \times MN×M大小的图,然后问用无数个1×21 \times 21×2大小的砖头把这个图填充满的方案有多少个。

【题解】

因为数据范围十分的小,所以可以用状压来枚举单排所有的情况,然后dp到最后一排,这做法也就是状压dp。我们可以规定砖头的竖放的上部分为1,砖头的横放或者是竖放的下部分为0。这样的话,单排的情况就本题列数最多的情况,最小状压出来的就是000000000000000000000000000000000,而最大的是111111111111111111111111111111111。而仔细想一下就会发现最后一行因为不能为竖放的上部分,也就是不能为1,那么最后一行的情况必定全都是0。又都能发现,当前 iii行 只会与 i−1i-1i1行 有关,因为理论上 i−1i-1i1行 我们是已经规定好了摆放的方式了的,那么 iii行 的摆放方式是受限于 i−1i-1i1行 的。例如 i−1i-1i1行 和 iii行 上下对应的格子都是1,这种就肯定是冲突的,当前 iii行 的这种情况肯定也是不允许的。所以我需要一开始的时候预处理出所有 i−1i-1i1行 的情况,即上一行如果是这样这样的,那当前行可以怎样怎样的。然后再是使用dp进行循环每一行枚举所有所有情况,让后进行状态转移。而状压的操作是用到了位运算的,在下面的代码中。i<(1<<m)便是可能性所状压出的二进制,一共有(1<<m)-1个;i>>j&1则是取j+1上的二进制,计算当 i−1i-1i1行 和 iii行 或位运算后,0个数的情况,当0的个数是为偶数时,是允许的,而0的个数时奇数时,是不被允许的,我们需要记录判断合并后允许的结果。j&Ki−1i-1i1行 和 iii行 与运算后的情况,都是0才是被允许的。j|k即用到之前预处理的信息。而最后一行必定全都是0,所以f[n][0]f[n][0]f[n][0]就是最终的结果。

时间复杂度:O(4MN)O(4^MN)O(4MN)

#include<iostream>
#include<cstring>
#include<sstream>
#include<string>
#include<cstdio>
#include<cctype>
#include<vector>
#include<queue>
#include<cmath>
#include<stack>
#include<list>
#include<set>
#include<map>
#include<algorithm>
#define fi first
#define se second
#define MP make_pair
#define P pair<int,int>
#define PLL pair<ll,ll>
#define lc (p<<1) 
#define rc (p<<1|1)    
#define MID (tree[p].l+tree[p].r)>>1
#define Sca(x) scanf("%d",&x)
#define Sca2(x,y) scanf("%d%d",&x,&y)
#define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define Scl(x) scanf("%lld",&x)
#define Scl2(x,y) scanf("%lld%lld",&x,&y)
#define Scl3(x,y,z) scanf("%lld%lld%lld",&x,&y,&z)
#define Pri(x) printf("%d\n",x)
#define Prl(x) printf("%lld\n",x)
#define For(i,x,y) for(int i=x;i<=y;i++)
#define _For(i,x,y) for(int i=x;i>=y;i--)
#define FAST_IO std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define STOP system("pause")
#define ll long long
const int INF=0x3f3f3f3f;
const ll INFL=0x3f3f3f3f3f3f3f3f;
const double Pi = acos(-1.0);
using namespace std;
template <class T>void tomax(T&a,T b){ a=max(a,b); }  
template <class T>void tomin(T&a,T b){ a=min(a,b); }
int n,m;
ll f[12][1<<12];
bool in_s[1<<12];

int main(){
	while(~Sca2(n,m)&&n){
		for(int i=0;i<(1<<m);i++){
			bool cnt=0,has=0;
			for(int j=0;j<m;j++){
				if(i>>j&1) has|=cnt,cnt=0;
				else cnt^=1;
				in_s[i]=has|cnt?0:1;
			}
		}
		f[0][0]=1;
		for(int i=1;i<=n;i++)    // 第i行
			for(int j=0;j<(1<<m);j++){  // 第i行的状态
				f[i][j]=0;        //下面循环的是i-1行的状态
				for(int k=0;k<(1<<m);k++) if((j&k)==0&&in_s[j|k]) f[i][j]+=f[i-1][k];
			}
			
		Prl(f[n][0]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值