牛客 哈尔滨理工大学软件与微电子学院第八届程序设计竞赛同步赛(高年级)小乐乐积木 DFS+DP压缩

链接: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;
}

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值