小乐乐搭积木
题目描述:
小乐乐想要给自己搭建一个积木城堡。积木城堡我们假设为n*m的平面矩形。小乐乐现在手里有1*2,2*1两种地砖。小乐乐想知道自己有多少种组合方案。
输入描述:
第一行输入整数n,m。(1<=n,m<=10)
输出描述:
输出组合方案数。
输入
2 3
输出
3
说明
输入
1 3
输出
0
输入
2 5
输出
8
题意:
给你一个h*w的矩形,用一个1*2和2*1的小矩形去填充,问有多少种填充方法,不考虑对称性。
题解:
1.DFS部分
实际上是在枚举第i行的放置方法,由此便可以确定出该行及上一行的状态。
对于第i行,状态(参数next_stat)的定义是指,前i-1行完全放满,第i行的所有位置是否放置(0,1表示)组成的二进制序列,转化为十进制数后所代表的状态。
放置方法,总共存在三种,
1. 竖直放置
2. 水平放置
3. 不放置
以竖直放置举例说明:
假设第i行的第3列位置是要竖直放置,则说明它的前一行该列位置必然是0,放置后这一行的状态必然变成了1,所以上一行的状态应该是prev_stat << 1,该行是(next_stat << 1)|1,直到column == w,即从左到右开始枚举,枚举到了最右列,说明该放置方案完毕。
本质上是枚举的第i行的放置方案,而放置方案实际上确定了上下两行的状态。
prev_stat,next_stat初始均为0,第一次移位确定的是左边第一个位置的状态,这样随着移位的进行这个状态不断的往高位迁移,这样在以后的移位,或运算中保证保持不变。也就是说,prev_stat,next_sta分别表示上一行和当前行从左到当前列的0,1状态
2.DFS+DP部分
记dp[i][next_stat]为第i-1行全满且第i行列的状态为next_stat时的种数,便有如下递推关系:
dp[i][next_stat] = sigma(dp[i-1][prev_stat]);
其中(next_stat, prev_stat)整体作为一个放置方案, 这样dp[h][(1 << w) -1]即是答案了
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); 竖直放置
意味着 当前行的当前列放置,上一行的当前列未被放置
DFS(column+2,(next_stat << 2)| 3,(prev_stat << 2)|3);水平放置
意味着 当前行的当前列和下一列放置,上一行的上一行的上一列都被放置过 (next_stat << 2)表示当前列和下一列的状态为0 (next_stat << 2)|3 当前列和下一列的状态为1
当column = w时保存状态
对于初始时的dp值,可以假设第0行全满,第一行只有两种放法:
1. 水平放置 column = column + 2, next_stat << 2 | 3;
2. 不放置 column = column + 1, next_stat << 1;
另外,利用滚动数组,可以减少空间的开销
还有一个可以提高较率的地方,当输入的 w > h 时,对调,因为横向(列)的运算是指数
级的, 而列向的(行)是线性的.
交换的话还用到一个小知识 用 ^ 进行交换
if(w > h)///这是一个优化 因为 列是指数级的行是线性级别的
{
w ^= h;
h ^= w;
w ^= h;
}
//还可以写成
w^=h^=w^=h //与上面是等价的
还有一个小的骚操作
if((h*w)%2 == 1)///h,w都是奇数一定不行
{
printf("0\n");
return 0;
}
//等价于
if((h*w)%2 == 1)///h,w都是奇数一定不行
return 0*printf("0\n");
以上题解 部分借鉴于银河里的星星,后面又加有自己的见解
AC代码
#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)///这是一个优化 因为 列是指数级的行是线性级别的
{
w ^= h;
h ^= w;
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;//滚动数组 只有两行
DFS(0,0,0);
memset(a[(t+1)%2],0,sizeof(a[0]));/// 小知识 memset(数组名,清空的值,sizeof(大小)) 这里每次只清空一行
}
printf("%lld\n",a[t][(1 << w) -1]);
return 0;
}
打表代码
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int mx = 1e9+5;
#define MAX (1<<11)+1
# include <stdio.h>
long long a[11][11]= {0,1,0,1,0,1,0,1,0,1,0,1,2,3,5,8,13,21,34,55,89,144,0,3,0,11,0,41,0,153,0,571,0,1,5,11,36,95,281,781,2245,6336,18061,51205,0,8,0,95,0,1183,0,14824,0,185921,0,1,13,41,281,1183,6728,31529,167089,817991,4213133,21001799,0,21,0,781,0,31529,0,1292697,0,53175517,0,1,34,153,2245,14824,167089,1292697,12988816,108435745,1031151241,8940739824,0,55,0,6336,0,817991,0,108435745,0,14479521761,0,1,89,571,18061,185921,4213133,53175517,1031151241,14479521761,258584046368,3852472573499,0,144,0,51205,0,21001799,0,8940739824,0,3852472573499,0};
int main()
{
int n,m;
scanf("%d%d",&n,&m);
printf("%lld\n",a[n-1][m-1]);
return 0;
}