我是一名铺砖匠——铺砖问题汇总(DP)
鉴于多次遇到铺砖问题,因此目前遇到对此类问题进行总结:
铺砖问题主要分类:
1.简单的一维铺砖问题
2.二维铺砖问题
2*1的砖是否可以铺满N*M的地面
3.二维铺砖铺满有多少种方法
a.2*1的砖铺满2*M的地面有多少种方法?
b. 2*1的砖铺满N*M的地面有多少种方法?
『简单的一维铺砖问题』
【问题描述】
有一长度为N(1<=N<=10)的地板,给定两种不同瓷砖:一种长度为1,另一种长度为2,数目不限。要将这个长度为N的地板铺满,一共有多少种不同的铺法?(蓝桥杯算法训练试题)
【解题思路】
一道简单的递推或者说是DP的题目
N = 1时,只有一种方法 一块长为1的砖
N = 2时,两种方法 两块长为1的砖,一块长为2的砖
N = 3时,三种方法 三块长为1的砖,先一块长为1的砖,后一块长为2的砖,先一块长为2的砖,后一块长为1的砖。
以N=3举例,我们可以这样看
a.先铺好了一块1格的地面,那么再铺一块长为2的砖就可以达到3格
b.先铺好了一块2格的地面,那么再铺一块长为1的砖就可以达到3格
即第i格地面的铺设方式为 第i-1和第i-2的方式的和。
从上面我们可以逐渐发现递推公式 或 状态转移方程 :dp[i] = dp[i-1]+d。p[i-2];
【代码】
#include <bits/stdc++.h>
using namespace std;
int dp[50];
int main()
{
int n;
cin >> n;
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= 40; i++)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
cout << dp[n];
return 0;
}
『二维是否可铺满问题』
【问题描述】
某年夏天,位于希格玛大厦四层微软亚洲研究院对办公楼的天井进行了一次大 规模的装修.原来的地板铺有 N×M 块正方形瓷砖,这些瓷砖都已经破损老化了,需要予以 更新.装修工人们在前往商店选购新的瓷砖时,发现商店目前只供应长方形的瓷砖,现在的 一块长方形瓷砖相当于原来的两块正方形瓷砖, 工人们拿不定主意该买多少了, 读者朋友们 请帮忙分析一下:能否用 1×2 的瓷砖去覆盖 N×M 的地板呢?
【解题思路】
考虑N,M的奇偶性
A.N*M为奇数,N和M都是奇数,1*2的瓷砖必定不可覆盖
B.N和M至少有一个是偶数,可以设M为偶数(若N为偶数可以交换N和M,保证M为偶),可以知道用1*2的砖铺1*M的砖必定可以铺满,需要M/2块砖。这样N*M就可以转化为重复铺N次1*M的地面的情况
【代码】
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n,m;
cin>>n>>m;
if(n*m%2 == 1){
cout<<"No";
}else{
if(n%2 == 0)
swap(n,m);
cout<<m/2*n;
}
return 0;
}
『二维铺满有多少种方式』
Ⅰ.2*1的砖铺满2*M的地面,有多少种方式(DP)
【问题描述】
求用1*2的瓷砖覆盖2*M的地板油几种方式?
【解题思路】
1*2的砖有两种铺法——横着或者竖着

A.第一块瓷砖竖着放的时候,问题就可以转化为 用1*2的砖铺设剩下的2*(M-1)地面的方式数
B.第一块瓷砖横着放的时候(必然有另一块横着放的瓷砖在其下面),问题就可以转化为在用1*2的砖铺设剩下的2*(M-2)地面的方式数
由此可得出状态转移方程:dp[i] = dp[i-1] + dp[i-2];
【代码】
#include <bits/stdc++.h>
using namespace std;
int dp[50];
int main()
{
int m;
cin >> m;
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= 40; i++)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
cout << dp[m];
return 0;
}
Ⅱ.2*1的砖铺满N*M的地面,有多少种方式(状态压缩+DP)
【题意简述】
有1*2的砖,问铺满N*M的地面需要多少种方式
【解题思路】
解题之前要明确的一些定义或者表示:
1. 考虑N*M的情况:
A.N*M为奇数时,无法铺设
B.N*M为偶数时,可以转化为上边的2*M的问题,可以看作是k*2*M(N = 2*k).故其实仔细分析两行就可以分析出整个地面的铺法。
2.对于所铺砖的表示:


图片来自(https://blog.csdn.net/u013480600/article/details/19569291)
这样表示是为了方便使用状态压缩(状态压缩自行学习),将铺砖的状态用0和1来表示。该矩阵的砖块摆放方法和该矩阵的二进制表示法是一一对应的。
3. 对于两行的是“匹配”的定义:
设当前行所保存的数是cur_,上一行保存的数为pre_,如果cur_和pre_对应的二进制数可以表示把两行的地面铺满,则说其是匹配的。
举个栗子:
对于一个2*5的地面 0 11 11 (十进制15)和 1 11 11 (十进制31)就是合法的,而15和15就是非法的。

砖块摆放有如下的关系: 如果当前位置(i,j)横着放,状态为11,那么上一行(i-1,j)也必须是11,之后我们要考虑(i,j+2) 如果当前位置(i,j)竖着放,状态为1,上一行(i-1,j)为0,之后我们要考虑(i,j+1) 如果当前位置(i,j)不放,那么上一行此位置(i-1,j)为1,当前位置为0,之后我们要考虑(i,j+1)
4.对于DP状态的定义:
dp[i][num]:在第i层为num时(在上述例子中 15和30 就是num)的方式数。
整体思路:
1.每行有三种选择,横着放,竖着放, 不放,如果用1表示横着放和竖着放的第二个,0表示竖着放的第一个和不放,每次都是两行之间的转换, 找出可以互相转换的状态就可以,采用深搜,找到所有pre_和cur_是匹配的二进制状态,将其存到pre[cnt],cur[cnt]这两个数组中。就拿上面例子来讲,可以写pre[0] = 15,cur[0] = 31;
2.第0行可以看作是全部铺成1(看作不需要在代码中写出),那么第0行看作有1种铺砖方法,即dp[0][(1<<m)-1] = 1 ((1<<m)-1代表二进制全为1,想一想),作为递归基;
3.在循环中依次遍历在pre[cnt]和cur[cnt]保存下来的可以“匹配”二进制状态,状态转移方程:dp[i][cur[j]] += dp[i - 1][pre[j]];
最后要求的结果就是dp[n][(1<<m)-1]
【代码】(一些代码定义会注释)
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int Max = 5e6;
long long dp[15][1 << 12];
int pre[Max], cur[Max];
int cnt, n, m;
void solve(int l, int pre_, int cur_)
{
if (l > m)
return;
else if (l == m)//保存匹配好的状态
{
pre[cnt] = pre_;
cur[cnt++] = cur_;
return;
}
solve(l + 2, (pre_ << 2) | 3, (cur_ << 2) | 3);
//横着放置
//每次 pre_和cur_ 按位或3 是为了将(i,j)和(i,j+1)置1,表示横铺砖,铺完以后考虑(i,j+2)
solve(l + 1, (pre_ << 1), (cur_ << 1) | 1);
//竖着放置
//每次 cur_ 按位或1 是为了将(i,j)置1,表示竖铺砖,铺完以后考虑(i,j+1)
solve(l + 1, (pre_ << 1) | 1, (cur_ << 1)); //不放置方块
}
int main()
{
while (scanf("%d%d", &n, &m) == 2 && n && m)
{
cnt = 0;
if (n < m)
swap(n, m);
solve(0, 0, 0);//使用深搜,找到所有“匹配”的状态
memset(dp, 0, sizeof(dp));
dp[0][(1 << m) - 1] = 1;//假想第0行是全1状态
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < cnt; j++)
dp[i][cur[j]] += dp[i - 1][pre[j]];
}
printf("%lld\n", dp[n][(1 << m) - 1]);
//cout << dp[n][(1 << m) - 1]<<"\n";
}
return 0;
}
/*
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
*/
/*
ans
1
0
1
2
3
5
144
51205
*/
智能推荐
我叫张东升,我是一名Android程序员,我有话要说
我叫张东升,记得上学的那会听邻村叔叔阿姨说,邻村的狗子做了程序员,每次狗子回村感觉跟暴发户一样,年少多金又有钱,说话超级好听,超级喜欢狗子这样的人,就是头发有点少,当时听狗子是这样描述的: 于是,从小对立志要做一个程序员,好好学习大学的时候选择计算机专业。毕业之后去某互联网公司做了Android程序员,兢兢业业勤勤恳恳的老实工作,梦想有一天想狗子那样。 开始的时候刚到这家公司,感觉程序这个行业真好...
铺瓷砖
问题描述 你是一位施工队的工长,根据设计师的要求准备为一套设计风格独特的房子进行室内装修。 房子的客厅大小为 n x m,为保持极简的风格,需要使用尽可能少的 正方形 瓷砖来铺盖地面。 假设正方形瓷砖的规格不限,边长都是整数。 请你帮设计师计算一下,最少需要用到多少块方形瓷砖? 示例1: 示例2: 示例3: 题目分析: 这个问题最先出现在萨姆·劳埃德的《谜题百科全书》中,书中对这个问...
铺瓷砖
一、前言 问题来源LeetCode 1240,难度:困难 问题链接:https://leetcode-cn.com/problems/tiling-a-rectangle-with-the-fewest-squares 二、题目 你是一位施工队的工长,根据设计师的要求准备为一套设计风格独特的房子进行室内装修。 房子的客厅大小为 n x m,为保持极简的风格,需要使...
铺瓷砖
看似好像是最小公倍数,BUT分数怎么会有最小公倍数呢 问题的实质是要我们求两个分数的最小公倍数。 首先,我们要知道,整数a和b的最小公倍数是a*b/gcd(a,b); 那么怎样来求分数的最小公倍数呢? 我们可以先将两个分数通分,分母变为t1,然后再求分子的最小公倍数t2。 那么答案就是t2/t1, 注:最后要约分...
猜你喜欢
铺瓷砖
问题的实质是要我们求两个分数的最小公倍数。 首先,我们要知道,整数a和b的最小公倍数是a*b/gcd(a,b); 那么怎样来求分数的最小公倍数呢? 我们可以先将两个分数通分,分母变为t1,然后再求分子的最小公倍数t2。 那么答案就是t2/t1, 注:最后要约分....
目标检测随笔。anchor的生成
目标的真实边界(ground_truth bounding box)。 而以像素为中心生成多个大小和宽高比(aspect ratio)的边界框,称为anchor box。基于深度学习的目标检测不使用传统的滑窗生成所有的窗口作为候选区域,FasterRCNN提出的RPN网络,处理较少但准确的候选区域。锚框即可理解为某个检测点处的候选区域。 假设图像高hhh,宽为www。以每个像素为中心,大小s&is...
数据结构与算法C++描述(14)---二叉搜索树
1、二叉搜索树的概念 二叉搜索树是一棵可能为空的二叉树,一棵非空的二叉搜索树有满足如下特征: 二叉树中所有的键值都是唯一的; 根节点所有左子树的键值(如果有的话)小于根节点的键值; 根节点所有右子树的键值(如果有的话)大于根节点的键值; 根节点的左右子树也都是二叉搜索树。 三个二叉搜索树如下: 2、二叉搜索树的C++描述 对于二叉搜索树,主要操作有插入一个元素(Insert)删除键值为k的元素(D...
数据库
什么是数据库? 简单的说,数据库就是一个存放数据的仓库,这个仓库是按照一定的数据结构(数据结构是指数据的组织形式或数据之间的联系)来组织,存储的,我们可以通过数据库提供的多种方法来管理数据库里的数据。 数据库诞生于1950年,随着信息技术的发展和人类社会的不断进步,特别是2000年后,数据库不在仅仅是存储和管理数据了,而转变成用户所需要的各种数据管理的方式,数据库有很多种类和功能,从最简单的存储有...
Centos7搭建Hadoop3.1.3完全分布模式
详细搭建可以参考我的Hadoop2.8.0安装 准备 本文下载的是3.1.3版本的Hadoop 关闭防火墙 虚拟机的准备 安装3个虚拟机并实现ssh免密码的登录 安装3个centos7虚拟机 安装3个机器,机器分别叫master slave1 slave2 在/etc/hostname下修改主机名 其他两台也是一样 修改主机映射 修改/etc/hosts文件 修改这3台机器的/etc/hosts文...
