【备战秋招】每日一题:2023.05.31-华为OD机式-第二题-园区规划

news/2025/2/22 23:06:08/

在线评测链接:P1310

题目描述

塔子哥带领着一支拥有激情和创新的团队,决定建设一个全新的园区。他们经过深入调研和思考,最终选择了一块荒地,并开始了紧张而充满挑战的规划和建设工作。在大家的共同奋斗下,新园区建成了,它不仅令人刮目相看的现代化设计,更彰显着这个团队的勇气和实干精神。园区的诞生,也为附近居民和企业带来了无限机遇和福祉。

现在塔子哥打算购买一块 m m m × \times × n n n 大小的土地,用于建设新土地。他希望将这块土地规划为尽可能少的正方形区域。他想起原先这个问题在acm竞赛中非常熟悉,但是由于他已经退役n年了,所以想寻求大家的帮助。

请注意,规划完这块土地以后,应该满足一个状态:没有任何一个正方形能放入!

输入描述

输入第一行为一个整数 m m m

输入第二行为一个整数 n n n

其中1 ≤ \leq m m m ≤ \leq 13 13 13 1 1 1 ≤ \leq n n n ≤ \leq 13 13 13.

输出描述

输出为一个整数,表示最少能规划块区域的数量.

样例1

输入

3
2

输出

3

解释

最少能规划块 3 3 3个区域。
先规划 1 1 1 2 2 2 × \times × 2 2 2
剩下区域规划 2 2 2 1 1 1 × \times × 1 1 1

样例2

输入

13
11

输出

6

解释

最少能规划块 6 6 6.个区域。

先规划 1 1 1 7 7 7 × \times × 7 7 7 1 1 1 6 6 6 × \times × 6 6 6

再规划 2 2 2 4 4 4 × \times × 4 4 4 1 1 1 5 5 5 × \times × 5 5 5

最后剩下 1 1 1 1 1 1 × \times × 1 1 1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LkyMPpok-1687435245505)(/split.png)]

思路:搜索+剪枝

给定一个 n ∗ m n*m nm 的矩形,我们需要从中分割出若干个正方形,要求正方形个数最少,求该个数。

这道题初看很容易认为是用分治的做法,通过枚举分割长度将矩形分成两块,分别求两块最少能分割的正方形区域再相加,并不断递归求解。

先详细说明下错误的分治的做法:

假设当前矩形的长宽分别为 a 、 b a、b ab,且有 a > b a \gt b a>b

  1. 枚举分割长度 i i i ( 1 ≤ i < a ) (1 \le i \lt a) (1i<a),从而得到两个大小的矩形 i ∗ b i*b ib ( a − i ) ∗ b (a-i)*b (ai)b
  2. 将两个矩形当成子问题,即求解两个矩形最少能分割出多少正方形,于是又回到第一步
  3. 直到到达边界条件 a = b a=b a=b ,此时能分割一个正方形,返回
  4. 在每次子问题求解完毕后,取两个子问题和的最小值作为当前矩形的最小值

但实际上看下样例2就会发现这样的解法是错误的,其分割方法很不规则,而分治的方法得到的分割方法很规则(因为每次都是整齐的分割成两个矩形,而样例2的分割是不整齐的)。分治在样例2上求出来的答案一般来说是8而不是6(可以自己尝试一下)。

由于样例2分割的不规则性,在发现分治无效后,也难以想到其它有效的解法。于是尝试使用搜索+剪枝求解。

(这其实是一个NP问题,但是当我们在比赛时,我们既不了解该问题,也不能想到有效的解法,就只能尝试用搜索方法尽可能拿分)

搜索规则如下:

定义 m a t r i x [ i ] [ j ] = { f a l s e , t r u e } matrix[i][j]=\left\{false,true\right\} matrix[i][j]={false,true} 表示该点是否已经被分割为了一个正方形

  1. 找到一个 m a t r i x [ i ] [ j ] = f a l s e matrix[i][j]=false matrix[i][j]=false的点,即该点未被分割为正方形
  2. 尝试以该点为正方形第一个点(即左上角点)分割正方形,即枚举正方形长度,尝试分割一个固定边长的正方形
  3. 将分割的区域标记为 t r u e true true,表示已经分割了正方形
  4. 重新回到第1步,直到无法找到未被标记的点

既然用了搜索,那肯定逃不掉的就是剪枝了,剪枝的方法有很多,这里提出几个主要的剪枝,有其它更优秀剪枝方法的可以自行添加:

  1. 记录当前已经找到的最小的答案,记为 a n s ans ans,在搜索过程中,如果当前统计的分割的正方形数(记为 c n t cnt cnt),有 c n t ≥ a n s cnt \ge ans cntans,说明继续找下去答案也不会更优了,可以直接回退
  2. 枚举分割正方形的边长时,从大往小枚举,这样能够尽早找到一个较小的答案,从而在后续搜索中减少搜索量

搜索的时间复杂度很难衡量,所以这里就不进行时间复杂度的计算了。

类似题目推荐

代码

C++

#include <iostream>
#include <cstdio>
using namespace std;bool matrix[15][15];
int m,n;
int ans=1e9;bool check(int x,int y,int len){for(int i=0;i<len;++i){for(int j=0;j<len;++j){if(matrix[x+i][y+j]) return false;}}return true;
}void fill(int x,int y,int len){// 采用异或方法进行填充/取消填充 for(int i=0;i<len;++i){for(int j=0;j<len;++j){matrix[x+i][y+j]^=1;}}
} void dfs(int x, int y, int cnt){if(cnt>=ans) return;// 剪枝,当前记录数已经≥已记录的最小答案,不再进行搜索if(x==m+1){// 填充完毕,更新答案 ans=cnt;return;}if(y>n) dfs(x+1,1,cnt);bool full=true;for(int i=y;i<=n;++i){// 从当前行的第y个格子开始枚举,找到第一个没有填充的格子 if(!matrix[x][i]){// 当前格子未填充,尝试填充正方形 full=false;for(int j=min(n-i+1,m-x+1);j>=1;--j){// 枚举填充正方形的边长,从长边开始枚举 if(check(x,i,j)){// 判断从第x行第i个格子开始能不能填充边长为j的长方形 fill(x,i,j);// 填充dfs(x,y+j,cnt+1);// 填充完一个正方形,尝试下一次填充fill(x,i,j);// 取消填充 }}break;// 尝试在当前格子填充正方形的所有情况已经全部考虑,直接弹出 }}if(full) dfs(x+1,1,cnt);// 当前行都填充了,搜索下一行 
}int main(){scanf("%d %d",&m,&n);dfs(1,1,0);printf("%d",ans);return 0;
}

python

def check(x, y, length):# 判断从第x行第y个格子开始能否填充边长为length的正方形for i in range(length):for j in range(length):if matrix[x+i][y+j]:return Falsereturn Truedef fill(x, y, length):# 采用异或方法进行填充/取消填充for i in range(length):for j in range(length):matrix[x+i][y+j] ^= 1def dfs(x, y, cnt):global ansif cnt >= ans:return  # 剪枝,当前记录数已经≥已记录的最小答案,不再进行搜索if x == m + 1:ans = cnt  # 填充完毕,更新答案returnif y > n:dfs(x + 1, 1, cnt)full = Truefor i in range(y, n + 1):# 从当前行的第y个格子开始枚举,找到第一个没有填充的格子if not matrix[x][i]:  # 当前格子未填充,尝试填充正方形full = Falsefor j in range(min(n - i + 1, m - x + 1), 0, -1):# 枚举填充正方形的边长,从长边开始枚举if check(x, i, j):fill(x, i, j)  # 填充dfs(x, y + j, cnt + 1)  # 填充完一个正方形,尝试下一次填充fill(x, i, j)  # 取消填充break  # 尝试在当前格子填充正方形的所有情况已经全部考虑,直接跳出循环if full:dfs(x + 1, 1, cnt)  # 当前行都填充了,搜索下一行m, n = map(int, input().split())
matrix = [[False] * 15 for _ in range(15)]
ans = 1e9
dfs(1, 1, 0)
print(ans)

Java

import java.util.Scanner;public class Main {static boolean[][] matrix = new boolean[15][15];static int m, n;static int ans = (int) 1e9;static boolean check(int x, int y, int len) {// 判断从第x行第y个格子开始能否填充边长为len的正方形for (int i = 0; i < len; ++i) {for (int j = 0; j < len; ++j) {if (matrix[x + i][y + j])return false;}}return true;}static void fill(int x, int y, int len) {// 采用异或方法进行填充/取消填充for (int i = 0; i < len; ++i) {for (int j = 0; j < len; ++j) {matrix[x + i][y + j] ^= true;}}}static void dfs(int x, int y, int cnt) {if (cnt >= ans)return; // 剪枝,当前记录数已经≥已记录的最小答案,不再进行搜索if (x == m + 1) {ans = cnt; // 填充完毕,更新答案return;}if (y > n)dfs(x + 1, 1, cnt);boolean full = true;for (int i = y; i <= n; ++i) { // 从当前行的第y个格子开始枚举,找到第一个没有填充的格子if (!matrix[x][i]) { // 当前格子未填充,尝试填充正方形full = false;for (int j = Math.min(n - i + 1, m - x + 1); j >= 1; --j) { // 枚举填充正方形的边长,从长边开始枚举if (check(x, i, j)) { // 判断从第x行第i个格子开始能不能填充边长为j的正方形fill(x, i, j); // 填充dfs(x, y + j, cnt + 1); // 填充完一个正方形,尝试下一次填充fill(x, i, j); // 取消填充}}break; // 尝试在当前格子填充正方形的所有情况已经全部考虑,直接跳出循环}}if (full)dfs(x + 1, 1, cnt); // 当前行都填充了,搜索下一行}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);m = scanner.nextInt();n = scanner.nextInt();dfs(1, 1, 0);System.out.println(ans);scanner.close();}
}

Go

package mainimport "fmt"var matrix [15][15]bool
var m, n int
var ans = int(1e9)func check(x, y, length int) bool {// 判断从第x行第y个格子开始能否填充边长为length的正方形for i := 0; i < length; i++ {for j := 0; j < length; j++ {if matrix[x+i][y+j] {return false}}}return true
}func fill(x, y, length int) {// 采用异或方法进行填充/取消填充for i := 0; i < length; i++ {for j := 0; j < length; j++ {matrix[x+i][y+j] = !matrix[x+i][y+j]}}
}func dfs(x, y, cnt int) {if cnt >= ans {return // 剪枝,当前记录数已经≥已记录的最小答案,不再进行搜索}if x == m+1 {ans = cnt // 填充完毕,更新答案return}if y > n {dfs(x+1, 1, cnt)}full := truefor i := y; i <= n; i++ {// 从当前行的第y个格子开始枚举,找到第一个没有填充的格子if !matrix[x][i] {// 当前格子未填充,尝试填充正方形full = falsefor j := min(n-i+1, m-x+1); j >= 1; j-- {// 枚举填充正方形的边长,从长边开始枚举if check(x, i, j) {fill(x, i, j)         // 填充dfs(x, y+j, cnt+1)   // 填充完一个正方形,尝试下一次填充fill(x, i, j)         // 取消填充}}break // 尝试在当前格子填充正方形的所有情况已经全部考虑,直接跳出循环}}if full {dfs(x+1, 1, cnt) // 当前行都填充了,搜索下一行}
}func min(a, b int) int {if a < b {return a}return b
}func main() {fmt.Scan(&m, &n)dfs(1, 1, 0)fmt.Println(ans)
}

Js

function check(x, y, length) {// 判断从第x行第y个格子开始能否填充边长为length的正方形for (let i = 0; i < length; i++) {for (let j = 0; j < length; j++) {if (matrix[x + i][y + j]) {return false;}}}return true;
}function fill(x, y, length) {// 采用异或方法进行填充/取消填充for (let i = 0; i < length; i++) {for (let j = 0; j < length; j++) {matrix[x + i][y + j] = !matrix[x + i][y + j];}}
}function dfs(x, y, cnt) {if (cnt >= ans) {return; // 剪枝,当前记录数已经≥已记录的最小答案,不再进行搜索}if (x === m + 1) {ans = cnt; // 填充完毕,更新答案return;}if (y > n) {dfs(x + 1, 1, cnt);}let full = true;for (let i = y; i <= n; i++) {// 从当前行的第y个格子开始枚举,找到第一个没有填充的格子if (!matrix[x][i]) {// 当前格子未填充,尝试填充正方形full = false;for (let j = Math.min(n - i + 1, m - x + 1); j >= 1; j--) {// 枚举填充正方形的边长,从长边开始枚举if (check(x, i, j)) {fill(x, i, j); // 填充dfs(x, y + j, cnt + 1); // 填充完一个正方形,尝试下一次填充fill(x, i, j); // 取消填充}}break; // 尝试在当前格子填充正方形的所有情况已经全部考虑,直接跳出循环}}if (full) {dfs(x + 1, 1, cnt); // 当前行都填充了,搜索下一行}
}function main() {const readline = require("readline");const rl = readline.createInterface({input: process.stdin,output: process.stdout,});rl.on("line", (line) => {const input = line.split(" ").map(Number);m = input[0];n = input[1];rl.close();}).on("close", () => {matrix = new Array(15).fill(false).map(() => new Array(15).fill(false));dfs(1, 1, 0);console.log(ans);process.exit(0);});
}main();

http://www.ppmy.cn/news/530892.html

相关文章

Linux学习[17]bash学习深入3---万用字符特殊符号---数据流重导向

文章目录 前言1. 万用字符2. 特殊字符3. 数据流重导向3.1标准输出3.2 标准输入 总结 前言 这篇博客是对之前在查找的时候涉及到的一些通配符(bash里面就是万用字符)的整理。这个为后面管线相关打一个基础。 1. 万用字符 这里整理了一个表格&#xff0c;后面配上相关实例。 符…

衡阳老家电器

自制 三种手摇发电机&#xff0c;闹钟⏰定时器 CRT康佳老彩电检修

外贸企业如何寻找厨房家电厂家一手货源?

外贸企业如何寻找厨房家电厂家一手货源&#xff1f; 好的一手货源每年能给卖家省很多钱&#xff0c;相对同行来说&#xff0c;也有更强的溢价空间&#xff0c;如果后期想要进行产品创新的话也会比较方便。常见的拿货方式有以下几种&#xff1a; 一、批发网拿货 新手在没有多…

修家电

最近我的手机坏了&#xff0c;到修理的地方发现很贵很贵的&#xff0c;最后&#xff0c;就自己回家休息&#xff0c;呵呵&#xff0c;没想到发现很简单&#xff0c;是啊&#xff0c;好歹我也是做过硬件的啊。。。

家电用品Electric Appliances

前言 加油 原文 家电用品常用会话 ❶ Milk will keep for several days in a refrigerator. 牛奶在冰箱里可以保存几天。 ❷ Will she cook dinner this evening? 她今晚做晚饭吗? ❸ The pie is not very hot. Can you microwave it a bit more? 这个饼不是很热的,你…

2022年Q1小家电行业趋势报告——厨房小家电篇

“懒人经济”和“单身经济”愈发流行&#xff0c;叠加疫情下“宅经济”的催化&#xff0c;小家电这一细分领域在近两年迅速崛起&#xff0c;以“空气炸锅”为代表的厨房小家电成为了无数“躺平”群体的“福音”。同时&#xff0c;疫情下促使消费者加强对健康概念的关注&#xf…

做强礼品经济韧性与活力,金秋10月第30届深圳礼品展来袭!

今年以来&#xff0c;面对复杂多变的形势和挑战&#xff0c;我国长期积累的雄厚物质基础和超大市场规模优势明显&#xff0c;展示了强劲的经济韧性。上半年深圳礼品展春季展圆满召开&#xff0c;为市场注入强劲动力&#xff0c;更印证礼品市场热度不减、供需两旺。下半年适逢各…

LeetCode 刷题 3. 无重复字符的最长子串

LeetCode 刷题 3. 无重复字符的最长子串 题目解题思路思路及算法代码 题目 给定一个字符串s&#xff0c;找出其中不包含重复字符的最长子串。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc"&#xff0c;所以其长度为 3…