链接:https://ac.nowcoder.com/acm/contest/301/B
来源:牛客网
题目描述
小乐乐想要给自己搭建一个积木城堡。
积木城堡我们假设为n*m的平面矩形。
小乐乐现在手里有1*2,2*1两种地砖。
小乐乐想知道自己有多少种组合方案。
分割线
用DP数组记录,记录当前行和上一行的每列状态,再用dfs来枚举每一行关于列的子集。
先来介绍下本题的一些变量命名:
对于第i行,状态(参数next_stat)的定义是指,前i-1行完全放满,第i行的所有位置是否放置(0表示无积木,1表示有积木)组成的二进制序列,转化为十进制数后所代表的状态。
column表示列 next_stat表示当前行的当前列的状态 prev_stat表示上一行的当前列的状态 还是指的列的状态。
a[2][MAX]优化dp[i][next_stat]为第i-1行全满且第i行列的状态为next_stat时的种数,即用a[t][(1<<w)-1来表示]最后得到的dp[h][(1<<w)-1]即是总的方案。
下面再弄清楚放置积木的方式:
1:不放置
DFS(column+1,next_stat << 1, (prev_stat << 1)|1);
意味着当前行的当前列不放置,上一行的当前列已经放置过,next_stat << 1 表示 当前行的当前列的状态为0 ;prev_stat << 1)|1 表示上一行的当前列的状态为1 (| 按位或,有一出一,全零才零)。
2:竖直放置
DFS(column+1,(next_stat << 1)|1,prev_stat << 1);
竖直放置 意味着 当前行的当前列放置,上一行的当前列未被放置。
3:水平放置
DFS(column+2,(next_stat << 2)| 3,(prev_stat << 2)|3);
水平放置 3的二进制为11,column+2意味着当前行的当前列和下一列放置;(next_stat << 2|3)表示当前行的当前列和下一列放置, (prev_stat << 2)|3 上一行的当前列和上一行的下一列状态为1。(若上一行有一状态为0,不能水平)
以水平放置为例://....
在上一行有0态的情况下,我们不能水平放置,所有上行对应列必是3(11)态,以上同理。
当column = w时保存状态。
对于初始时的dp值,可以假设第0行全满从而保证从第一行开始时无影响,第一行只有两种放法:
1. 水平放置 column = column + 2, next_stat << 2 | 3;
2. 不放置 column = column + 1, next_stat << 1;
因为是二维,可以利用滚动数组,可以减少空间的开销
还有一个可以提高较率的地方,当输入的 w > h 时,对调,因为横向(列)的运算是指数,选择小的做循环和dfs。
如果面积是奇数,而每次我们能放的积木只有面积为2的,所以总的矩形面积若是为奇,无法得到方案。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int mx = 1e9+5;
#define MAX (1<<11)+1
int w;
ll a[2][MAX];
int t;
///对于第i行,状态(参数next_stat)的定义是指,前i-1行完全放满,第i行的所有位置是否放置(0,1表示)组成的二进制序列,转化为十进制数后所代表的状态。
///DFS枚举第i行的放置情况(保证第i-1行放满),由此确定该行及第i-1行的状态,为节约空间,使用滚动数组实现
void DFS(int column,int next_stat,int prev_stat)/// column表示列 next_stat表示当前行的当前列的状态 prev_stat表示上一行的当前列的状态 还是指的列的状态
{
if(column == w)
{
a[t][next_stat] += a[(t+1)%2][prev_stat];
return;
}
if(column + 1 <= w)
{
DFS(column+1,next_stat << 1, (prev_stat << 1)|1); ///不放置 意味着 当前行的当前列不放置,上一行的当前列已经放置过,next_stat << 1 表示 当前行的当前列的状态为0 (prev_stat << 1)|1 表示上一行的当前列的状态为1
DFS(column+1,(next_stat << 1)|1,prev_stat << 1); ///竖直放置 意味着 当前行的当前列放置,上一行的当前列未被放置
}
if(column + 2 <= w)
DFS(column+2,(next_stat << 2)| 3,(prev_stat << 2)|3);///水平放置 意味着 当前行的当前列和下一列放置,上一行的这两列都被未放置过 (next_stat << 2)表示当前列和下一列的状态为0 (next_stat << 2)|3 当前列和下一列的状态为1
}
int main()
{
int h;
scanf("%d%d",&h,&w);/// h表示行,w表示列
if((h*w)%2 == 1)///h,w都是奇数一定不行
{
printf("0\n");
return 0;
}
if(w > h)//这是一个优化 因为列是指数级的行是线性级别的,行列交换不影响结果
swap(w, h);
memset(a,0,sizeof(a));
t = 0;
a[t][(1 << w) - 1] = 1;//初始化,相当于第0行全满,所以只有该状态数为1,其他状态方法均为0. (1<<w)-1 表示 每一位的二进制都为1
for(int i = 1 ; i <= h ; i++)///枚举行
{
t = (t+1)%2;//滚动数组 只有两行,(t+1)%2保证轮换
DFS(0,0,0);
memset(a[(t+1)%2],0,sizeof(a[0]));//每次行时重新初始当前行
}
printf("%lld\n",a[t][(1 << w) -1]);
return 0;
}