算法:计数类dp

news2025/2/24 6:04:22

文章目录

      • 一、举个栗子
        • 例子1:爬楼梯问题
        • 例子2:不同路径
        • 例子3:计数子序列
      • 二、基本思路
      • 三、典型例题
        • 一、ACWing:900. 整数划分
          • 1、解法一
            • 1.1、状态转移方程
            • 1.2、参考代码 O(n³) 超时
          • 2、解法二:类似完全背包问题
            • 1.1、状态转移方程
            • 1.2、解释
            • 1.3、降维优化
            • 1.4、参考代码

  计数类动态规划(Counting DP)是一种用来解决计数问题的动态规划技术,它通常用于求解在给定条件下满足某种性质的组合或序列的总数。 计数类DP问题的特点是要求计算所有可能情况的数量,而不是求最值或是否存在这样的情况。 当然我们在使用计数类dp的时候,没必要去和线性dp区分开来,会用就行。
  我们先举个栗子,来说明计数类dp是个啥,然后再来说明思路。

一、举个栗子

例子1:爬楼梯问题

  假设你在爬楼梯,每次你可以爬1个或2个台阶。给定楼梯的总台阶数n,你有多少种不同的方法可以爬到楼顶?

分析:让我们用dp[i]表示到达第i阶楼梯的方法数量。如果我们考虑最后一步,我们可以从第i-1阶跨一步到达第i阶,或者从第i-2阶跨两步到达第i阶。因此,到达第i阶的方法数是到达第i-1阶和第i-2阶方法数的和。

状态转移方程dp[i] = dp[i-1] + dp[i-2]

初始化dp[1] = 1, dp[2] = 2


例子2:不同路径

  一个机器人位于一个m x n网格的左上角,机器人每次只能向下或向右移动一步。机器人试图达到网格的右下角。问总共有多少条不同的路径?

分析:定义dp[i][j]为到达网格中(i, j)位置的路径数量。要到达(i, j),机器人只能从(i-1, j)向下走,或从(i, j-1)向右走,所以dp[i][j]是这两个来源的路径数之和。

状态转移方程 dp[i][j] = dp[i-1][j] + dp[i][j-1]

初始化:网格的最上方dp[0][j] = 1和最左方dp[i][0] = 1,因为只有一种方式到达。


例子3:计数子序列

  给定一个字符串,计算不同的子序列个数。子序列是从给定序列中删除一些字符(也可以不删除)后形成的新序列。

分析:这个问题稍微复杂一点。我们可以使用dp[i]表示考虑到字符串的第i个字符时的不同子序列个数。对于每个新字符,它可以选择加入之前的子序列中,或者不加入。

状态转移:如果当前字符之前没出现过,dp[i] = 2 * dp[i-1]。如果当前字符之前出现过,需要减去上一次该字符出现时的子序列个数,以避免重复计数。

二、基本思路

  计数类dp的基本思路不好说,因为本质上就是要找到一个状态以及状态转移,使得满足要求计算所有可能情况的数量 并且 避免重复计算。 这也是最难得一步。

计数类DP的基本思路涉及以下几个步骤:

  1. 定义状态:根据问题的特性,合理定义状态,表示达到当前状态的可能情况数量。状态通常涉及考虑的元素个数、已选择元素的性质(如总和、最大值等)以及其他约束条件。

  2. 确定状态转移方程:根据问题的性质,确定从已知状态到未知状态的转移方式。状态转移方程描述了在当前决策下,如何通过之前的状态来计算当前状态的值。

  3. 初始化:正确初始化DP数组,特别是基础情况(通常是递归的边界条件),这些基础情况直接决定了递推的起点。

  4. 计算顺序:根据状态之间的依赖关系确定计算顺序,确保在计算某个状态之前,它依赖的状态已经被计算。

  5. 结果汇总:根据问题要求,从DP数组中提取最终结果。有时可能需要遍历DP数组的某些部分来汇总最终的计数结果。

三、典型例题

一、ACWing:900. 整数划分

900. 整数划分
在这里插入图片描述
  这类问题的难点再于找到状态和状态转移方程,以及看出来它是一个动态规划题。

1、解法一

用题目本身最接的方式去思考:
  对于数n和 数 n+1的划分方法有没有关系?我们定义dp[i]表示数字i的不同划分方法,那么dp[n+1]dp[n]的关系是什么?在直观上考虑n+1n在大小上相差1,因此n+1n的划分关系是:

  • n+1n多了一种n+1本身
  • n+1将1拿出来,剩下的等同于n的划分
  • 拿出来的1,可以与n的某些划分的中的数合并,可以得到新的划分。我们来考察n+1的划分
    • n+1(n,1) , (n-1,2) , (n-2,3) , (n-3,4) , (n-4,5) ···((n+1)/2,(n+1)/2) ,其中()表示一种划分,括号中的前一个数是怎么划分的不管,后一个数就是一个确切的数,并且满足,前一个数的划分的最后一个数≥后一个数。对于n而言,很显然这些划分对于n中都是没有的。因为我们单独枚举了n+1的最后一个数,那么留给前面的数尽管可能在n中存在这样的序列,但是加上了n+1枚举的最后一个数 就不可能存在这样的序列了。这样问题就出现了,我们怎么才可能找到到底有多少种?
      因此既然有问题,我们不妨将关系定义为二维:dp[i][j]表示 数字i的划分中 以 j结尾的划分数量,那么可以定义数字i的划分数量dp[i][0]等于dp[i][1]加到dp[i][i]。那么我们就可以解决了~

  实际上当我们定义为二维的时候,我们就可以发现,我们实际上是枚举划分的最后一个位置来分类讨论求解,在分类讨论时,我们将当时的状态数存下来以便于后续转移!

1.1、状态转移方程
  • 初始化dp[i][i]=dp[i][0]=1,表示i本身这种划分。
  • dp[i][j]+=dp[i-j][m]:其中j>1&&j<=i-j并且m>=j,并且m<=i-j (i-j , j)
  • dp[i][1]=dp[i-1][0](减少计算)
1.2、参考代码 O(n³) 超时
#include<bits/stdc++.h>
using namespace  std;
int dp[1001][1001];//不存在的情况划分数为 0
int main(void){
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    int N;
    cin>>N;
    dp[1][0]=dp[1][1]=1;
    int p=1e9+7;
    for(int i=2;i<=N;++i){
        dp[i][i]=1;//i的情况
        dp[i][1]=dp[i-1][0];
        dp[i][0]=(dp[i][i]+dp[i][1])%p;//全1的情况
        for(int j=2;j<=i/2;++j){//i的划分中 以j为尾数
            for(int m=j;m<=i-j;++m){//遍历i-j中满足条件的所有可能情况,至少要以j结尾
                dp[i][j]=(dp[i][j]+dp[i-j][m])%p;//只需要i-j的划分以m>=j结尾就行,当然m>i-j就无意义了。
            }
            dp[i][0]=(dp[i][0]+dp[i][j])%p;//累计数量
        }
    }
    cout<<dp[N][0];
    return 0;
}
2、解法二:类似完全背包问题

链接:完全背包问题解析
转化问题去思考:
  我们很容易想到的一个点是,实际上我们在划分时,只需要划分的数字集合不一样就行了,也就是数字集合的总和能达到n就行了,而不需要满足所谓的n1>=n2>=n3···这种形式上的顺序要求。那么这个问题就转化成了一个类似完全背包的问题了:

  • 对于1~n,n个数字想象成n个物品,体积大小就是数字大小,物品可以无限次被装
  • 一个背包只能装满总体积为n的物品

这个问题就完全被转化成为一个完全背包问题,求体积为n的背包有多少种不同的装物品的情况。

在这里插入图片描述
而这里转化为求:目标是达到背包最大承重的情况下,选取物品可以无限次,选取物品的不同种类数。

本题和完全背包问题的区别,就变成了是求最大利益,还是求种类数。

1.1、状态转移方程

定义 dp[i][j]表示考虑前i个物品,物品不限量装满体积为j的背包时,可以装物品的不同情况数。
一般的:

for(int i=1;i<=n;++i)//遍历物品
for(int j=0;j<=n;++j)//遍历体积
for(int k=0;k*i<=j;++k)//遍历物品承装次数
	dp[i][j]+=dp[i-1][j-k*i];//物品i可以考虑 k 次

但实际上,根据以上推导可以发现,当考虑前i个物品时,体积为j-i的背包的所有可能情况,加一个物品i,就对应于背包体积为j至少有一个物品i所能对应的所有情况了(因为根据以上循环可知,j-i背包的所有可能情况,包含了没有物品i的情况,以及包含了能有则有物品i的情况,那么将其转移至j背包,j背包就包含了至少有一关i物品所能对应的所有情况了)。优化相当于合并情况,避免重复计算。

  • 不加入物品i时种数:dp[i-1][j]

  • 加入物品i但不限次时种数:dp[i][j-i]

  • dp[i][j]=dp[i-1][j]+dp[i][j-i]

1.2、解释
  1. 不加入物品i时的种数:dp[i-1][j]
  2. 加入物品i但不限次数时的种数:dp[i][j-i]

对于这两个状态转移方程,它们分别代表了以下含义:由于一个包含i,一个不包含i,这两种划分必然是互不包含的。

  1. dp[i-1][j]:这表示考虑前i-1种物品(或者说数字),使得整数划分的总和为j的方法数量。在这个状态中,我们不包括i个物品(或者说数字i),即我们看不包含i能划分成j的种数。

  2. dp[i][j-i]:这表示考虑前i种物品(或者说数字),其中至少包含一个i,使得整数划分的总和为j的方法数量。由于我们考虑的是完全背包问题,即每个数字可以被无限次使用,所以在当前状态下加入i后,总和为j-i的状态可以直接转移到总和为j的状态。简单地说,dp[i][j-i]就是考虑j-i总和时,还能继续加入i以到达j的种数,且这种情况情况至少包含了一个i,即j-ij的转移,而其余数量的i包含在j-i的划分中。

将这两个状态结合起来,我们得到整数j的总的划分方法数量:

dp[i][j] = dp[i-1][j] + dp[i][j-i]

这个方程结合了两种情况:一种是不包含数字i的划分种数,另一种是至少包含一个数字i的划分种数。这两种情况构成了所有可能的情况。

为了清晰起见,假设我们有一个整数划分问题,要将数字5分解为不大于5的整数之和。使用上面的状态转移方程:

  • 如果我们不使用数字5,那么dp[5][5]需要包含不使用5得到5的所有方法,即dp[4][5]
  • 如果我们至少使用一个5,那么我们看在不超过5的情况下,有多少种方法得到0,这是一个空划分,只有一种方式,所以dp[5][5]还要加上dp[5][0]

这样,dp[5][5]就等于dp[4][5](不使用5的方法)加上dp[5][0](至少使用一个5的方法)。当然,dp[5][0]在这种情况下是1,因为只有一种方法用5得到5

这就是为什么整数划分问题在考虑加入物品(或数字)时使用的是dp[i][j-i],它反映了完全背包中的无限使用特性。

1.3、降维优化

背包问题降维优化是很简单的,因为每次使用只使用之前的,因此可以降维:

for(int i=1;i<=n;++i)//遍历物品i
	for(int j=i;j<=n;++j)//遍历背包大小,从小到大遍历,因为dp[i][j]可以由dp[i][j-i]转移,因此小j需要先计算出来。
		dp[j]=(dp[j]+dp[j-i])%p;
cout<<dp[n];
1.4、参考代码
#include<bits/stdc++.h>
using namespace  std;
int main(void){
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    int N;
    cin>>N;
    int dp[1001]={};
    dp[0]=1;
    int p=1e9+7;
    for(int i=1;i<=N;++i){
        for(int j=i;j<=N;++j)
            dp[j]=(dp[j]+dp[j-i])%p;
    }
    cout<<dp[N];
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1586027.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOv8 推理脚本--置信度保留多位浮点数 特征图可视化

效果 特征图可视化: 4位浮点数: 原始2位浮点数4位浮点数推理 --detect.py 说明 在进行改动前,请大家先阅读下 基础入门篇 | YOLOv8 项目【训练】【验证】【推理】最简单教程 | YOLOv8必看 | 最新更新,直接打印 FPS,mAP50,75,95 ,确保会用我给的推理脚本。 YOLO( ):…

ChatGPT在日常生活与工作中的应用,以及Hulu AI 的探索之旅

ChatGPT在日常生活与工作中的应用&#xff0c;以及Hulu AI 的探索之旅 &#x1f4ac;ChatGPT 的多面应用&#x1f4ac;Hulu AI&#xff1a;一个AI工具聚合平台的探索平台优势为何选择Hulu AI&#xff1f;珍稀优惠 &#x1f4ac;结束语 在数字化快速发展的当下&#xff0c;人工智…

苍穹外卖开发笔记(1.项目介绍和开发环境)

目录 一、项目介绍二、环境搭建1、web管理端前端部署2、后端环境搭建3、数据库搭建4、前后端联调5、导入接口文档 三、完善登录功能四、学习知识1、前端发送的请求&#xff0c;是如何请求到后端服务的&#xff1f; 一、项目介绍 二、环境搭建 由于本项目主要点在于学习后端开发…

React + three.js 3D模型面部表情控制

系列文章目录 React 使用 three.js 加载 gltf 3D模型 | three.js 入门React three.js 3D模型骨骼绑定React three.js 3D模型面部表情控制 示例项目(github)&#xff1a;https://github.com/couchette/simple-react-three-facial-expression-demo 示例项目(gitcode)&#xff…

ASP.NET Core 标识(Identity)框架系列(二):使用标识(Identity)框架生成 JWT Token

前言 JWT&#xff08;JSON Web Token&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;用于在网络上以 JSON 对象的形式安全地传输信息。 JWT 通常用于在用户和服务器之间传递身份验证信息&#xff0c;以便在用户进行跨域访问时进行身份验证。 JWT 由…

力扣--图论/Prim1584.连接所有点的最小费用

思路分析&#xff1a; 初始化&#xff1a;获取点的数量&#xff0c;并创建两个辅助数组 adjvex 和 lowcost&#xff0c;分别用于记录最小生成树的边信息和每个顶点到最小生成树的距离。Prim算法循环&#xff1a;在每一次循环中&#xff0c;选择一个未加入最小生成树的顶点 k&a…

HTML5+CSS3+JS小实例:图片切换特效之模糊变清晰

实例:图片切换特效之模糊变清晰 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, i…

2023-2024爱分析·信创厂商全景报告|爱分析报告

在中央及地方政府的信创政策推动下&#xff0c;我国信创部分领域正在从“试点验证”迈向“规模推广”阶段。随着国产替换的深化&#xff0c;爱分析观察到&#xff0c;在需求侧&#xff0c;企业对信创产品的需求逐渐融合更丰富的业务诉求以及未来数智规划&#xff0c;正从“同类…

HCIA综合实验(DHCP + OSPF + ACL + VLAN + NAT)

一.实验拓扑 二.实验要求 1.ISP路由器仅配置IP地址 2.内网基于192.168.1.0/24 网段进行IP划分 3.R1/R2之间使用OSPF做到全网全通&#xff0c;单区域 4.PC1-PC4可以使用DHCP获取地址 5.PC2-PC4可以访问PC5&#xff0c;PC1不行 6.R2出口只拥有一个公网IP 7.test-1设…

蓝桥杯(5):python动态规划DP[2:背包问题]

1 0-1背包介绍【每件物品只能拿1件或者不拿】 1.1 简介 贪心是不可以的&#xff01;&#xff01;&#xff01; 1.2 状态 及状态转移 转移解释&#xff1a;要么不选 则上一个直接转移过来【dp[i-1][j]】&#xff0c;要么是选这个之后体积为j 则上一个对应的就是【dp[i-1][j-wi]…

4.Hexo 页面属性和模板设置

Frontmatter frontmatter基本上是可以定义的有关不同文件的信息&#xff0c;本质上是元数据 frontmatter是我们可以分配给每个内容页面的信息 在Hexo中创建文件时&#xff0c;Hexo主题可以使用该信息以不同的方式显示该内容 当在Hexo创建了一个文件&#xff0c;在source文件夹…

【MATLAB源码-第12期】基于matlab的4FSK(4CPFSK)的误码率BER理论值与实际值仿真。

1、算法描述 4FSK在频移键控&#xff08;FSK&#xff09;编码的基础上有所扩展。FSK是一种调制技术&#xff0c;它通过在不同频率上切换来表示不同的数字或符号。而4FSK则是FSK的一种变种&#xff0c;表示使用了4个不同的频率来传输信息。 在4FSK中&#xff0c;每个数字或符号…

信号继电器DX-31B 额定值1A 柜内安装,板前接线 约瑟JOSEF

系列型号 DX-31B信号继电器DX-31BJ信号继电器 DX-32A信号继电器DX-32AJ信号继电器 DX-32B信号继电器DX-32BJ信号继电器 DX-31A信号继电器DX-33/1信号继电器 DX-33/2信号继电器DX-33/3信号继电器 DX-33/4信号继电器DX-33/5信号继电器 ​用途 DX信号继电器用于电力系统继…

本地自动备份的设置方法,终于不担心数据丢失了

前言 这几天陆续有小伙伴在公众号后台发送【同步】&#xff0c;想要找找看有没有适合本地的同步/备份软件。今天小白终于有时间给小伙伴们更新这类型的教程了。 本地自动备份的软件肯定是有的&#xff0c;只是好像使用的人比较少&#xff0c;所以就没有太多教程。 虽然说可以…

Excel 防止数字变为E+的技巧

方式一&#xff1a;开始选项卡 ⇒ 分数 方式二&#xff1a;设置单元格格式 ⇒ 自定义 ⇒ 0 方式三 设置单元格格式为纯文本后&#xff0c;在粘贴数据当数字过长的时候(例如身份证号)&#xff0c;超过15位之后的数字都会变成0。 此时可以在数字前添加一个符号&#xff0c;例如 …

2024-4-11-arm作业

汇编实现三个灯的闪烁 源代码&#xff1a; .text .global _start _start: 时钟使能LDR r0,0x50000A28ldr r1,[r0]orr r1,r1,#(0x1<<4)str r1,[r0]设置PE10输出LDR r0,0x50006000ldr r1,[r0]bic r1,r1,#(0x3<<20)orr r1,r1,#(0x1<<20)str r1,[r0]设置PE1…

Green Hills 自带的MULTI调试器查看R7芯片寄存器

Green Hills在查看芯片寄存器时需要导入 .grd文件。下面以R7为例&#xff0c;演示一下过程。 首先打开MULTI调试器&#xff0c;如下所示View->Registers&#xff1a; 进入如下界面&#xff0c;选择导入寄存器定义文件.grd&#xff1a; 以当前R7芯片举例&#xff08;dr7f7013…

天诚智慧校园管理系统,变革高校物联网锁数智化通行新模式

三月草长莺飞&#xff0c;四月柳绿莺啼&#xff0c;在万物复苏的美好时节&#xff0c;历经半年的精心酝酿与匠心打磨&#xff0c;全场景AIoT解决方案服务商——江苏新巢天诚智能技术有限公司&#xff08;以下简称“天诚”&#xff09;正式推出新一代高校数智化通行管理平台——…

如何将CSDN的文章以PDF文件形式保存到本地

1.F12 打开开发者工具窗口 2.console下输入命令 (function(){$("#side").remove();$("#comment_title, #comment_list, #comment_bar, #comment_form, .announce, #ad_cen, #ad_bot").remove();$(".nav_top_2011, #header, #navigator").remove…

syncfusion-diagram:demo1如何实现

xmlns:syncfusion"http://schemas.syncfusion.com/wpf" xmlns:stencil"clr-namespace:Syncfusion.UI.Xaml.Diagram.Stencil;assemblySyncfusion.SfDiagram.WPF"当我们进入syncfusion的diagram中&#xff0c;可以看到&#xff0c;一个非常炫酷的例子 不仅实…