永远不可能学会的动态规划之状压DP(小乐乐搭积木)

                                     小乐乐搭积木

题目描述:

小乐乐想要给自己搭建一个积木城堡。积木城堡我们假设为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;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值