【前缀和 记忆化搜索】LeetCode1444. 切披萨的方案数

2024-06-01 07:44

本文主要是介绍【前缀和 记忆化搜索】LeetCode1444. 切披萨的方案数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文涉及的基础知识点

C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
动态规划 记忆化搜索

LeetCode1444. 切披萨的方案数

给你一个 rows x cols 大小的矩形披萨和一个整数 k ,矩形包含两种字符: ‘A’ (表示苹果)和 ‘.’ (表示空白格子)。你需要切披萨 k-1 次,得到 k 块披萨并送给别人。
切披萨的每一刀,先要选择是向垂直还是水平方向切,再在矩形的边界上选一个切的位置,将披萨一分为二。如果垂直地切披萨,那么需要把左边的部分送给一个人,如果水平地切,那么需要把上面的部分送给一个人。在切完最后一刀后,需要把剩下来的一块送给最后一个人。
请你返回确保每一块披萨包含 至少 一个苹果的切披萨方案数。由于答案可能是个很大的数字,请你返回它对 10^9 + 7 取余的结果。
示例 1:
在这里插入图片描述

输入:pizza = [“A…”,“AAA”,“…”], k = 3
输出:3
解释:上图展示了三种切披萨的方案。注意每一块披萨都至少包含一个苹果。
示例 2:

输入:pizza = [“A…”,“AA.”,“…”], k = 3
输出:1
示例 3:

输入:pizza = [“A…”,“A…”,“…”], k = 1
输出:1

提示:
1 <= rows, cols <= 50
rows == pizza.length
cols == pizza[i].length
1 <= k <= 10
pizza 只包含字符 ‘A’ 和 ‘.’ 。

预处理

本题解点的坐标用(x,y)表示,而不是行列。
任何时间,待切的蛋糕一定保留原始蛋糕的右下角(cols-1,rows-1)。所以只需要枚举(left,top)。
垂直切:左边的部分,bottom一定和原始蛋糕bottom,相同。只需要枚举left,top,right。
水平且:上边的部分,right一定和原始蛋糕同。只需要枚举left,top,bottom。
vLeft[left][t][r] 记录(left,t,r,rows-1) 是否包括苹果。
vTop[left][[t][b] 记录(left,t,cols-1,b) 是否包括苹果。
计算vLeft的过程如下:
符合以下条件之一vLeft[left][t][r] 就有苹果:
一,(left,t)有苹果。
二,vLeft(left+1,t,r)有苹果。
三,vLeft(left,t+1,r)有苹果。
vTop类似。
m =max(rows,cols)。 空间复杂度O(mmm),时间复杂也是O(mmm)。

** 可以用二维前缀和计算左边(上边)的苹果数**。

动态规划

动态的状态表示

dp[k][left][top] 表示将蛋糕(left,top,cols-1,rows-1)切k次的方案数。
空间复杂度:O(k × \times × rows × \times × cols)

动态规划的转移方程

垂直切
dp[k][left][top] += F o r x : l e f t + 1 : c o l s − 1 v L e f t [ l e f t ] [ t o p ] [ x − 1 ] 有苹果 D p ( k − 1 , x , t o p ) \large For_{x:left+1:cols-1}^{vLeft[left][top][x-1]有苹果}Dp(k-1,x,top) Forx:left+1:cols1vLeft[left][top][x1]有苹果Dp(k1,x,top)
记忆化搜索: Dp是函数,如果dp有对应值,则返回dp的值。否则更新dp的值,并返回。避免重复计算。
水平切:
dp[k][left][top] += F o r x : t o p + 1 : r o w s − 1 v T o p [ l e f t ] [ t o p ] [ x − 1 ] 有苹果 D p ( k − 1 , l e f t , x ) \large For_{x:top+1:rows-1}^{vTop[left][top][x-1]有苹果}Dp(k-1,left,x) Forx:top+1:rows1vTop[left][top][x1]有苹果Dp(k1,left,x)
注意:送出的部分必须有一个苹果,包括的部分必须有k个苹果。

时间复杂度:O(k × \times × rows × \times × cols × \times × (rows+cols))

动态规划的初始值

dp[0][left][top] = (left,top,m_iC-1,m_iR-1)是否有苹果。 如果是记忆化搜索,还需要vHasDo 记录各状态是否处理。

动态规划的填表顺序

直接Do(k-1,0,0) 是记忆化搜索。从1到k 计算所有状态的值,是动态规划。记忆化搜索可以避免计算一些不需要计算的值。

动态规划的返回值

dp[k][0][0]

代码

核心代码

template<int MOD = 1000000007>
class C1097Int
{
public:C1097Int(long long llData = 0) :m_iData(llData% MOD){}C1097Int  operator+(const C1097Int& o)const{return C1097Int(((long long)m_iData + o.m_iData) % MOD);}C1097Int& operator+=(const C1097Int& o){m_iData = ((long long)m_iData + o.m_iData) % MOD;return *this;}C1097Int& operator-=(const C1097Int& o){m_iData = (m_iData + MOD - o.m_iData) % MOD;return *this;}C1097Int  operator-(const C1097Int& o){return C1097Int((m_iData + MOD - o.m_iData) % MOD);}C1097Int  operator*(const C1097Int& o)const{return((long long)m_iData * o.m_iData) % MOD;}C1097Int& operator*=(const C1097Int& o){m_iData = ((long long)m_iData * o.m_iData) % MOD;return *this;}C1097Int  operator/(const C1097Int& o)const{return *this * o.PowNegative1();}C1097Int& operator/=(const C1097Int& o){*this /= o.PowNegative1();return *this;}bool operator==(const C1097Int& o)const{return m_iData == o.m_iData;}bool operator<(const C1097Int& o)const{return m_iData < o.m_iData;}C1097Int pow(long long n)const{C1097Int iRet = 1, iCur = *this;while (n){if (n & 1){iRet *= iCur;}iCur *= iCur;n >>= 1;}return iRet;}C1097Int PowNegative1()const{return pow(MOD - 2);}int ToInt()const{return m_iData;}
private:int m_iData = 0;;
};template<class T = int>
class CPreSum2 {
public:template<class _Pr>CPreSum2(int rowCnt, int colCount, _Pr pr):m_iRowCnt(rowCnt),m_iColCnt(colCount){m_vSum.assign(rowCnt + 1, vector<int>(colCount + 1));for (int r = 0; r < rowCnt; r++) {for (int c = 0; c < colCount; c++) {m_vSum[r + 1][c + 1] = m_vSum[r][c + 1] + m_vSum[r + 1][c] - m_vSum[r][c] + pr(r, c);}}}T Get(int left, int top, int right, int bottom)const {return m_vSum[bottom + 1][right + 1] - m_vSum[top][right + 1] - m_vSum[bottom + 1][left] + m_vSum[top][left];}T GetTopLeft(int left, int top) { return Get(left, top, m_iColCnt - 1, m_iRowCnt - 1); }vector<vector<T>> m_vSum;const int m_iRowCnt, m_iColCnt;
};
class Solution {
public:int ways(vector<string>& pizza, int k) {m_iR = pizza.size();m_iC = pizza[0].size();CPreSum2<int> preSum(m_iR, m_iC, [&](int r, int c) {return 'A' == pizza[r][c]; });	m_dp.assign(k, vector<vector<C1097Int<>>>(m_iC, vector<C1097Int<>>(m_iR)));m_vHasDo.assign(k, vector<vector<bool>>(m_iC, vector<bool>(m_iR)));for (int left = 0; left < m_iC; left++) {for (int top = 0; top < m_iR; top++) {m_dp[0][left][top] = preSum.GetTopLeft(left, top)>0;m_vHasDo[0][left][top] = true;}}return Rec(preSum, pizza, k-1, 0, 0).ToInt();}C1097Int<> Rec( CPreSum2<int>& preSum,  vector<string>& pizza, int k, int left, int top) {auto& iRet = m_dp[k][left][top];if (m_vHasDo[k][left][top]) { return iRet; }m_vHasDo[k][left][top] = true;int cnt = preSum.Get(left, top, m_iC - 1, m_iR - 1);//当前蛋糕有多少苹果for (int x = left + 1; x <= m_iC - 1; x++) {//垂直切const int iSub = preSum.Get(left, top, x - 1, m_iR - 1);//送出多少苹果if (0 == iSub ) { continue; }if (cnt - iSub < k ) { continue; }iRet += Rec(preSum, pizza, k - 1, x, top);}for (int x = top + 1; x <= m_iR - 1; x++) {//水平切const int iSub = preSum.Get(left, top, m_iC - 1, x - 1);if (0 == iSub) { continue; }if (cnt - iSub < k) { continue; }iRet += Rec(preSum, pizza, k - 1, left, x);}return iRet;}vector<vector<vector<C1097Int<> >>> m_dp;vector<vector<vector<bool >>> m_vHasDo;int m_iR, m_iC;
};

VS自带单元测试

template<int MOD = 1000000007>
class C1097Int
{
public:C1097Int(long long llData = 0) :m_iData(llData% MOD){}C1097Int  operator+(const C1097Int& o)const{return C1097Int(((long long)m_iData + o.m_iData) % MOD);}C1097Int& operator+=(const C1097Int& o){m_iData = ((long long)m_iData + o.m_iData) % MOD;return *this;}C1097Int& operator-=(const C1097Int& o){m_iData = (m_iData + MOD - o.m_iData) % MOD;return *this;}C1097Int  operator-(const C1097Int& o){return C1097Int((m_iData + MOD - o.m_iData) % MOD);}C1097Int  operator*(const C1097Int& o)const{return((long long)m_iData * o.m_iData) % MOD;}C1097Int& operator*=(const C1097Int& o){m_iData = ((long long)m_iData * o.m_iData) % MOD;return *this;}C1097Int  operator/(const C1097Int& o)const{return *this * o.PowNegative1();}C1097Int& operator/=(const C1097Int& o){*this /= o.PowNegative1();return *this;}bool operator==(const C1097Int& o)const{return m_iData == o.m_iData;}bool operator<(const C1097Int& o)const{return m_iData < o.m_iData;}C1097Int pow(long long n)const{C1097Int iRet = 1, iCur = *this;while (n){if (n & 1){iRet *= iCur;}iCur *= iCur;n >>= 1;}return iRet;}C1097Int PowNegative1()const{return pow(MOD - 2);}int ToInt()const{return m_iData;}
private:int m_iData = 0;;
};template<class T = int>
class CPreSum2 {
public:template<class _Pr>CPreSum2(int rowCnt, int colCount, _Pr pr):m_iRowCnt(rowCnt),m_iColCnt(colCount){m_vSum.assign(rowCnt + 1, vector<int>(colCount + 1));for (int r = 0; r < rowCnt; r++) {for (int c = 0; c < colCount; c++) {m_vSum[r + 1][c + 1] = m_vSum[r][c + 1] + m_vSum[r + 1][c] - m_vSum[r][c] + pr(r, c);}}}T Get(int left, int top, int right, int bottom)const {return m_vSum[bottom + 1][right + 1] - m_vSum[top][right + 1] - m_vSum[bottom + 1][left] + m_vSum[top][left];}T GetTopLeft(int left, int top) { return Get(left, top, m_iColCnt - 1, m_iRowCnt - 1); }vector<vector<T>> m_vSum;const int m_iRowCnt, m_iColCnt;
};
class Solution {
public:int ways(vector<string>& pizza, int k) {m_iR = pizza.size();m_iC = pizza[0].size();CPreSum2<int> preSum(m_iR, m_iC, [&](int r, int c) {return 'A' == pizza[r][c]; });	m_dp.assign(k, vector<vector<C1097Int<>>>(m_iC, vector<C1097Int<>>(m_iR)));m_vHasDo.assign(k, vector<vector<bool>>(m_iC, vector<bool>(m_iR)));for (int left = 0; left < m_iC; left++) {for (int top = 0; top < m_iR; top++) {m_dp[0][left][top] = preSum.GetTopLeft(left, top)>0;m_vHasDo[0][left][top] = true;}}return Rec(preSum, pizza, k-1, 0, 0).ToInt();}C1097Int<> Rec( CPreSum2<int>& preSum,  vector<string>& pizza, int k, int left, int top) {auto& iRet = m_dp[k][left][top];if (m_vHasDo[k][left][top]) { return iRet; }m_vHasDo[k][left][top] = true;int cnt = preSum.Get(left, top, m_iC - 1, m_iR - 1);//当前蛋糕有多少苹果for (int x = left + 1; x <= m_iC - 1; x++) {//垂直切const int iSub = preSum.Get(left, top, x - 1, m_iR - 1);//送出多少苹果if (0 == iSub ) { continue; }if (cnt - iSub < k ) { continue; }iRet += Rec(preSum, pizza, k - 1, x, top);}for (int x = top + 1; x <= m_iR - 1; x++) {//水平切const int iSub = preSum.Get(left, top, m_iC - 1, x - 1);if (0 == iSub) { continue; }if (cnt - iSub < k) { continue; }iRet += Rec(preSum, pizza, k - 1, left, x);}return iRet;}vector<vector<vector<C1097Int<> >>> m_dp;vector<vector<vector<bool >>> m_vHasDo;int m_iR, m_iC;
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
《喜缺全书算法册》以原理、正确性证明、总结为主。
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

这篇关于【前缀和 记忆化搜索】LeetCode1444. 切披萨的方案数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1020393

相关文章

uniapp接入微信小程序原生代码配置方案(优化版)

uniapp项目需要把微信小程序原生语法的功能代码嵌套过来,无需把原生代码转换为uniapp,可以配置拷贝的方式集成过来 1、拷贝代码包到src目录 2、vue.config.js中配置原生代码包直接拷贝到编译目录中 3、pages.json中配置分包目录,原生入口组件的路径 4、manifest.json中配置分包,使用原生组件 5、需要把原生代码包里的页面修改成组件的方

【文末附gpt升级秘笈】腾讯元宝AI搜索解析能力升级:千万字超长文处理的新里程碑

腾讯元宝AI搜索解析能力升级:千万字超长文处理的新里程碑 一、引言 随着人工智能技术的飞速发展,自然语言处理(NLP)和机器学习(ML)在各行各业的应用日益广泛。其中,AI搜索解析能力作为信息检索和知识抽取的核心技术,受到了广泛的关注和研究。腾讯作为互联网行业的领军企业,其在AI领域的探索和创新一直走在前列。近日,腾讯旗下的AI大模型应用——腾讯元宝,迎来了1.1.7版本的升级,新版本在AI搜

代码随想录算法训练营第三十九天|62.不同路径 63. 不同路径 II 343.整数拆分 96.不同的二叉搜索树

LeetCode 62.不同路径 题目链接:62.不同路径 踩坑:二维的vector数组需要初始化,否则会报错访问空指针 思路: 确定动态数组的含义:dp[i][j]:到达(i,j)有多少条路经递推公式:dp[i][j] = dp[i-1][j] + dp[i][j-1]初始化动态数组:dp[0][0] = 1遍历顺序:从左到右,从上到下 代码: class Solution {pu

【建设方案】基于gis地理信息的智慧巡检解决方案(源文件word)

传统的巡检采取人工记录的方式,该工作模式在生产中存在很大弊端,可能造成巡检不到位、操作失误、观察不仔细、历史问题难以追溯等现象,使得巡检数据不准确,设备故障隐患得不到及时发现和处理。因此建立一套完善的巡检管理系统是企业实现精细化管理的一项重要工作。 基于GIS地理信息系统绘制常规巡检线路,设置线路巡检频率,当线路处于激活状态时,可根据已设置的频率自动生成巡检线路任务,并以消息的形式推送给执行人,

leetcode刷题(45)——35. 二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5] 示例 1: 输入: root = [

分布式锁实现方案-基于Redis实现的分布式锁

目录 一、基于Lua+看门狗实现 1.1 缓存实体 1.2 延迟队列存储实体 1.3 分布式锁RedisDistributedLockWithDog 1.4 看门狗线程续期 1.5 测试类 1.6 测试结果 1.7 总结 二、RedLock分布式锁 2.1 Redlock分布式锁简介 2.2 RedLock测试例子 2.3 RedLock 加锁核心源码分析 2.4

Android Apk瘦身方案1——R.java文件常量内联

R.java 文件结构 R.java 是自动生成的,它包含了应用内所有资源的名称到数值的映射关系。先创建一个最简单的工程,看看 R.java 文件的内容: R文件生成的目录为app/build/generated/not_namespaced_r_class_sources/xxxxxDebug/processXXXXDebugResources/r/com/xxx/xxx/R.java

CloudStack管理员文档 - 服务方案

用户创建一个实例可以又很多个选项来设定该实例的特性和性能。CloudStack提供以下几种方式: 服务方案,由管理员定义,提供了CPU速度,CPU数量,内存大小,根磁盘的标签,以及其他选项磁盘方案,由管理员定义,为主存储提供了磁盘大小和IOPS的选项网络方案,由管理员定义, 计算和磁盘方案 服务方案是CPU,内存,磁盘等虚拟硬件特性的集合。管理员可以创建各种服务方案,终端用户在创建虚拟机的时

【智能优化算法改进策略之局部搜索算子(五)—自适应Rosenbrock坐标轮换法】

1、原理介绍 作为一种有效的直接搜索技术,Rosenbrock坐标轮换法[1,2]是根据Rosenbrock著名的“香蕉函数”的特点量身定制的,该函数的最小值位于曲线狭窄的山谷中。此外,该方法是一种典型的基于自适应搜索方向集的无导数局部搜索技术。此法于1960年由Rosenbrock提出,它与Hooke-Jeeves模式搜索法有些类似,但比模式搜索更为有效。每次迭代运算分为两部分[3]: 1)

硬件上STM32F4xx兼容STM32F1xx的方案

前言 2020年开始,因为疫情,全球晶圆缺货,加上不少供应商屯芯片,导致ST的芯片价格一路飙涨,特别是STM32F1系列的单片机,价格涨的特别离谱,还缺货。。。。问了以下ST代理商,说STM32F1系列的属于168nm产品线的,正在被ST淘汰,让尽快用先进一点工艺的代替,手里有个项目用的STMF103VET6,代理商推荐先用STM32F401VE代替,国内现在右不少厂家可以pin2pin替代ST