蓝桥杯每日一题------背包问题(四)

前言

前面讲的都是背包的基础问题,这一节我们进行背包问题的实战,题目来源于一位朋友的询问,其实在这之前很少有题目是我自己独立做的,我一般习惯于先看题解,验证了题解提供的代码是正确的后,再去研究题解,这就给我自己养成了一种依赖的习惯。我害怕自己去挑战没有答案的问题,朋友问的这道题来源于一个小众网站,因此没有题解,出于试一试的态度,我也算比较轻松的做出来了,让我十分有满足感,十分感谢他。但是他好久之前问我的一道题,我现在还没有回他(sorry)。话不多说看题目吧。

数的分解

题目描述
把 A 分解成 B 个各不相同的正整数之和,并且要求每个正整数都不包含数字
2和4,一共有多少种不同的分解方法?注意交换几个整数的顺序被视为同一种方法,例如 1000+1001+18 和 1001+1000+18被视为同一种。
输入描述
第一行包一个整数 T,表示测试数据的规模。接下来 T 行每行 2 个整数A,B。
输出描述
对每个输入输出一个整数表示答案。
输入数据
2
10 2
2019 3
输出数据
2
40785
评测用例规模与约定
对于所有评测用例,1≤T≤10,1≤A≤2500,1≤B≤10。
这道题目类似于蓝桥杯的一道真题,那道真题是分解数字2019,可以去看一下,数的分解。因为那个题目只分解一个数字,所以采用dfs甚至for循环枚举都可以过。但是这道题目是分解好几个数字,而且分解后数字的个数也不是固定的,如果用dfs会超时。
那么试着考虑一下动态规划,因为无法为它划分为区间,状压,期望,树形,所以只能是普通的dp,那就用普通的dp的思考顺序进行思考。
定义dp数组
第一步:缩小规模。 考虑分解成B个整数,那么我用B当作数据规模。
第二步:限制。 需要记录当前B个数字之和是多少。除了这一个限制还有什么限制呢?选出来的数字不能包含2或4,这一个限制好考虑,只需要在选择的时候检查一遍就可以了。还有呢?还有需要保证选择的方案不会重复,这种限制最常见的是用a<b<c来约束,即后一个一定比前一个大,这样相当于规定了选择的顺序,但是如果是这样的话我需要知道前一个选择的是啥,这个时候就需要记录。其它限制应该没有了。
第三步:写出dp数组。 dp[i][j][k]表示当前选择了i个数字,所选数字之和为j且最后一个选择的数字是k时的?。这样要求啥?求方案数啊,一般求啥表示啥(也有特殊情况),所以‘?’表示的是方案数。
第四步:推状态转移方程。 dp[i][j][k]应该从哪里转移过来呢,必然是从前i-1个数字的状态转移,这个状态还应该考虑此时j和k的情况,当前可以选择的数字必然是比j小,比k大,假设当前选择的数字是p,则 k < p < j k<p<j k<p<j。所以 d p [ i ] [ j ] [ p ] + = d p [ i − 1 ] [ j − p ] [ k ] dp[i][j][p] += dp[i-1][j-p][k] dp[i][j][p]+=dp[i1][jp][k]
综上状态转移方程如下
d p [ i ] [ j ] [ p ] + = d p [ i − 1 ] [ j − p ] [ k ] dp[i][j][p] += dp[i-1][j-p][k] dp[i][j][p]+=dp[i1][jp][k]
考虑写代码了
第一步:确定好遍历顺序。 对于背包问题,一般第一个for遍历规模,第二个for遍历限制。但是我们的限制有两个,所以加上规模一共三层嵌套的for循环。
第二步:确定好转移位置。 对于当前可以选择的数字,只要比k大我都可以尝试在这一步选择,所以需要一个for循环遍历此时转移的数字。综上一共4层嵌套的for循环。那么代码如下

	int dp[][][] = new int[k+1][n+1][n+1];//n表示要分解的数字,k表示分解后的数字个数// TODO Auto-generated method stubfor(int i = 1;i <= n;i++) dp[1][i][i] = 1;for(int i = 1;i <= k;i++) {//10 遍历规模for(int j = 1;j <= n;j++) {//2500 遍历限制 for(int q = 1;q <= j;q++) {//if(check(q)) continue;//检查是否包含了2或4for(int p = q+1;p <= j;p++) {if(check(p)) continue;dp[i][j][p] += dp[i-1][j-p][q]; }}}}

可以算一下上述思路的时间复杂度是 O ( k ∗ n ∗ n ∗ n ) = 10 ∗ 2500 ∗ 2500 ∗ 2500 > 1 e 8 O(k*n*n*n)=10*2500*2500*2500>1e8 O(knnn)=10250025002500>1e8,会超时。那么在写上述思路的过程中你有没有发现一个问题。对于n范围内的数字我只有可能会选择一次,选择k个数字,并且数字之和恰好等于n,这像不像二维01背包问题?没错就是!按照刚刚所想重新思考dp数组。
定义dp数组
第一步:缩小规模。 对于n范围内的数字我只有可能会选择一次,小于n的所有数字都可以看作是一个物品,一共有n个物品。
第二步:限制。
限制1:选出来的数字不能包含2或4,这一个限制好考虑,只需要在选择的时候检查一遍就可以了,不需要新的维度。
限制2:选出来的数字个数不能超过B,需要一个维度来限制。
限制3:选出来的数字之和不能超过n,需要一个维度来限制。
第三步:写出dp数组。 dp[i][j][k]表示当前选择了i个数字,所选数字之和为k所选数字个数为j时的方案数。
第四步:推状态转移方程。 dp[i][j][k]应该从哪里转移过来呢,必然是从前i-1个数字的状态转移,如果第i个数字不选,则 dp[i][j][k]+= dp[i-1][j][k],如果选择第i个数字,则 dp[i][j][k]+= dp[i-1][j-1][k-i]
综上状态转移方程如下
i f ( k > i ) if(k>i) if(k>i) d p [ i ] [ j ] [ p ] = d p [ i − 1 ] [ j ] [ k ] + d p [ i − 1 ] [ j − 1 ] [ k − i ] dp[i][j][p] = dp[i-1][j][k]+dp[i-1][j-1][k-i] dp[i][j][p]=dp[i1][j][k]+dp[i1][j1][ki]
e l s e else else d p [ i ] [ j ] [ p ] = d p [ i − 1 ] [ j ] [ k ] dp[i][j][p] = dp[i-1][j][k] dp[i][j][p]=dp[i1][j][k]
考虑写代码了
第一步:确定好遍历顺序。 对于背包问题,一般第一个for遍历规模,第二个for遍历限制。但是我们的限制有两个,所以加上规模一共三层嵌套的for循环。
第二步:确定好转移位置。 当前第i个数字进行转移,所以不需要额外的for循环,代码如下

import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
public class Main{static int n,k,ans=0;//检查是不是带有2 或 4static Boolean  check(int parm) {while(parm>0) {int t=parm%10;if(t==2 || t==4) return true;parm/=10;}return false;}
public static void main(String[] args) {
//	f();Scanner scanner = new Scanner(System.in);int t = scanner.nextInt();long dp[][][] = new long[2500+1][10+1][2500+1];//当前考虑的物品,当前选择的物品个数,当前选择的物品的重量,当前物品选还是没选while(t-- > 0) {n=scanner.nextInt();k = scanner.nextInt();int sum=0;//从2500个物品里,选10个物品,且价值恰好为n		dp[0][0][0]=1;for(int i = 1;i <= n;i++) {dp[i][1][i] = 1;	dp[i][0][0] = 1;
//			dp[1][0][0][0] = 1;}for(int i = 1;i <= n;i++) {//for(int j = 1;j <= k;j++) {//for(int q = 1;q <= n;q++) {//if(check(i)) {dp[i][j][q] = dp[i-1][j][q];}else {if(q >= i)dp[i][j][q] = dp[i-1][j-1][q-i]+dp[i-1][j][q];elsedp[i][j][q] = dp[i-1][j][q];}	
//					System.out.println(dp[i][j][q] + " " + i + " " + j + " " + q);}}}long ans = 0;ans = dp[n][k][n]; System.out.println(ans);}
}
}

此时的时间复杂度是 O ( B ∗ A ∗ A ) = 10 ∗ 2500 ∗ 2500 = 62500000 < 1 e 8 O(B*A*A)=10*2500*2500=62500000<1e8 O(BAA)=1025002500=62500000<1e8,貌似可以,但是别忘了还有T,所以时间复杂度应该是 O ( B ∗ A ∗ A ∗ T ) = 10 ∗ 2500 ∗ 2500 ∗ 10 = 62500000 > 1 e 8 O(B*A*A*T)=10*2500*2500*10=62500000>1e8 O(BAAT)=102500250010=62500000>1e8,但是我们可以看到当n=2500,t=1时是不超时的,而对于其它的n<2500和k<=10,其实可以直接用dp[n][k][n]来表示,所以我们只需要预处理出n=2500和k=10的情况,然后针对其它样例直接输出就可以了。
超时的问题解决了,还有一个问题,不太常遇到的问题,空间超限,因为我们设的数组是三维的,空间大小将近1e9了。这里可以考虑用滚动dp来节省空间,滚动dp在背包问题(一)中进行过讲解,修改后的代码如下,

import java.util.Arrays;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;public class Main{static int n,k,ans=0;//检查是不是带有2 或 4static Boolean  check(int parm) {while(parm>0) {int t=parm%10;if(t==2 || t==4) return true;parm/=10;}return false;}
public static void main(String[] args) {
//	f();Scanner scanner = new Scanner(System.in);int t = scanner.nextInt();long dp[][][] = new long[2][10+1][2500+1];//当前考虑的物品,当前选择的物品个数,当前选择的物品的重量,当前物品选还是没选dp[0][0][0] = 1;dp[1][1][1] = 1;dp[1][0][0] = 1;n = 2500;k = 10;for(int i = 1;i <= n;i++) {//for(int j = 1;j <= k;j++) {//for(int q = 1;q <= n;q++) {//                             if(check(i)) {dp[i&1][j][q] = dp[(i-1)&1][j][q];}else {if(q >= i)dp[i&1][j][q] = dp[(i-1)&1][j-1][q-i]+dp[(i-1)&1][j][q];elsedp[i&1][j][q] = dp[(i-1)&1][j][q];}	
//				System.out.println(dp[i][j][q] + " " + i + " " + j + " " + q);}}}while(t-- > 0) {n=scanner.nextInt();k = scanner.nextInt();long ans = 0;ans = dp[n&1][k][n]; System.out.println(ans);}
}
}

这个网站的题目应该是对蓝桥杯题目进行了改编,不得不说改编的质量还是挺高的,再来另一个类似的题目

数的分解2

题目描述
将 N 拆分成 M 个正整数之和,总共有多少种拆分方法?注意交换顺序视为不同的方法,例如 2025 = 1000 + 1025 和 2025 = 1025 + 1000 就视为不同的方法。
输入描述
第一行包一个整数 T,表示测试数据的规模。接下来
T 行每行 2 个整数,N,M。
输出描述
对每个输入输出一个整数表示答案。由于答案可能会很大,请输出答案除以 109+7 的结果。
输入数据
2
40 5
1988 2
输出数据
82251
1987
评测用例规模与约定
对于所有评测用例,1≤T≤10,1≤N≤2500,1≤M≤10。
这道题和上一道题有什么区别呢?除了没有不能选包含2和4的数字的约数之外,还规定了不同的顺序视为不同的方案,这样就不是01背包了。因为对于01背包而言,10+11和11+10是一种方案。还是先按照dp的步骤考虑一下。
定义dp数组
第一步:缩小规模。 考虑分解成B个整数,那么我用B当作数据规模。
第二步:限制。 需要记录当前B个数字之和是多少。
第三步:写出dp数组。 dp[i][j]表示当前选择了i个数字,所选数字之和为j时的方案数。
第四步:推状态转移方程。 dp[i][j]应该从哪里转移过来呢,必然是从前i-1个数字的状态转移,这个状态还应该考虑此时j的情况,当前可以选择的数字必然是比j小,假设当前选择的数字是p,则 p < j p<j p<j。所以 d p [ i ] [ j ] + = d p [ i − 1 ] [ j − p ] dp[i][j] += dp[i-1][j-p] dp[i][j]+=dp[i1][jp]
综上状态转移方程如下
d p [ i ] [ j ] [ p ] + = d p [ i − 1 ] [ j − p ] dp[i][j][p] += dp[i-1][j-p] dp[i][j][p]+=dp[i1][jp]
考虑写代码了
第一步:确定好遍历顺序。 对于背包问题,一般第一个for遍历规模,第二个for遍历限制。
第二步:确定好转移位置。 对于当前可以选择的数字,只要比j小我都可以尝试在这一步选择,所以需要一个for循环遍历此时转移的数字。综上一共3层嵌套的for循环。那么代码如下

import java.util.Arrays;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;public class Main{static int n,k,ans=0;
public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int t = scanner.nextInt();n = 2500;k = 10;int mod = (int) (1e9+7);int dp[][] = new int[k+1][n+1];// TODO Auto-generated method stubfor(int i = 1;i <= n;i++) dp[1][i] = 1;for(int i = 1;i <= k;i++) {//10 遍历规模for(int j = 1;j <= n;j++) {//2500 遍历限制 for(int q = 1;q <= j;q++) {//dp[i][j] += dp[i-1][j-q]; dp[i][j] %= mod;}}}while(t-- > 0) {n=scanner.nextInt();k = scanner.nextInt();long ans = 0;ans = dp[k][n]; System.out.println(ans);}
}

该思路的时间复杂度是 O ( N ∗ M ∗ N ) = 2500 ∗ 2500 ∗ 10 < 1 e 8 O(N*M*N)=2500*2500*10<1e8 O(NMN)=2500250010<1e8,可以通过本题。
可以看见同一个思路,因为限制的降低,dp数组维数降低,时间复杂度就降低了。

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

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

相关文章

罗德里格斯公式简要介绍

一、罗德里格斯公式&#xff08;Rodrigues’ rotation formula&#xff09;是一个用于计算绕任意轴旋转向量的数学公式。它是由欧仁罗德里格斯&#xff08;Olinde Rodrigues&#xff09;在1840年提出的。这个公式在三维空间中描述了一个向量绕着单位向量旋转θ角度后的结果。 …

C++初阶:容器(Containers)list常用接口详解

介绍完了vector类的相关内容后&#xff0c;接下来进入新的篇章&#xff0c;容器list介绍&#xff1a; 文章目录 1.list的初步介绍2.list的定义&#xff08;constructor&#xff09;3.list迭代器&#xff08; iterator &#xff09;4.string的三种遍历4.1迭代器4.2范围for循环 5…

BatchNorm介绍:卷积神经网络中的BN

一、BN介绍 1.原理 在机器学习中让输入的数据之间相关性越少越好&#xff0c;最好输入的每个样本都是均值为0方差为1。在输入神经网络之前可以对数据进行处理让数据消除共线性&#xff0c;但是这样的话输入层的激活层看到的是一个分布良好的数据&#xff0c;但是较深的激活层…

MyBatis执行流程

MyBatis是一个流行的Java持久层框架&#xff0c;它封装了JDBC操作&#xff0c;使开发者可以通过XML或注解的方式映射SQL语句&#xff0c;并将POJO与数据库表之间进行映射。了解MyBatis的执行流程&#xff0c;可以帮助开发者更好地理解其内部工作机制&#xff0c;优化代码并解决…

企业微信自动推送机器人的应用与价值

随着科技的快速发展&#xff0c;企业微信自动推送机器人已经成为了企业数字化转型的重要工具。这种机器人可以自动推送消息、执行任务、提供服务&#xff0c;为企业带来了许多便利。本文将探讨企业微信自动推送机器人的应用和价值。 一、企业微信自动推送机器人的应用 企业微信…

无人机飞行控制系统功能,多旋翼飞行控制系统概述

飞行控制系统存在的意义 行控制系统通过高效的控制算法内核&#xff0c;能够精准地感应并计算出飞行器的飞行姿态等数据&#xff0c;再通过主控制单元实现精准定位悬停和自主平稳飞行。 在没有飞行控制系统的情况下&#xff0c;有很多的专业飞手经过长期艰苦的练习&#xff0…

「数据结构」串

串的定义和实现 串的定义 串: 即字符串&#xff0c;零个或多个字符组成的有限序列串的长度&#xff1a;串中字符的个数n空串&#xff1a;n0时的串子串&#xff1a;串中任意多个连续的字符组成的子序列主串&#xff1a;包含子串的串字符在主串中的位置&#xff1a;字符在串中的…

【Docker进阶】镜像制作-用Dockerfile制作镜像(一)

进阶一 docker镜像制作 文章目录 进阶一 docker镜像制作用dockerfile制作镜像dockerfile是什么dockerfile格式为什么需要dockerfileDockerfile指令集合FROMMAINTAINERLABELCOPYENVWORKDIR 用dockerfile制作镜像 用快照制作镜像的缺陷&#xff1a; 黑盒不可重复臃肿 docker…

嵌入式大厂面试题(1)—— CVTE

从本篇开始将会更新历年来各个公司的面试题与面经&#xff0c;题目来自于网上各个平台以及博主自己遇到的&#xff0c;如果大家有所帮助&#xff0c;帮忙点点赞和关注吧&#xff01; 岗位&#xff1a;嵌入式软件工程师。 面试时间&#xff1a;20分钟。 面试 1 、简历中写了做过…

Kafka 入门笔记

课程地址 概述 定义 Kafka 是一个分布式的基于发布/订阅模式的消息队列&#xff08;MQ&#xff09; 发布/订阅&#xff1a;消息的发布者不会将消息直接发送给特定的订阅者&#xff0c;而是将发布的消息分为不同的类别&#xff0c;订阅者只接受感兴趣的消息 消息队列 消息队…

HCIA-Datacom实验指导手册:4.3 实验三:网络地址转换配置实验

HCIA-Datacom实验指导手册&#xff1a;4.3 实验三&#xff1a;网络地址转换配置实验 一、实验介绍&#xff1a;二、 思考题与附加内容 一、实验介绍&#xff1a; NAT的作用&#xff1a; 1、很大程度提高网络安全性。 2、控制内外网网络联通性问题。 特点&#xff1a; 1&#…

JDK 11 vs JDK 8:探索Java的新特性和改进

随着技术的不断进步&#xff0c;Java开发工具包&#xff08;JDK&#xff09;也在不断演变&#xff0c;为开发者带来更高效、更安全的编程体验。在这篇文章中&#xff0c;我们将重点探讨JDK11相较于JDK 8所引入的一些新特性和改进&#xff0c;以便您能够更好地了解Java的最新发展…

leetcode:买卖股票最佳时机二

思路&#xff1a; 使用贪心算法&#xff1a;局部最优是将买卖过程中产生的正数进行相加&#xff0c;进而使得最后结果最大&#xff08;全局最优&#xff09;。 price [7,1,5,10,3,6,4] -6,4,5,-7,3,-2 正数相加就得到了最大 代码实现&#xff1a; 1.循环中下标从1开始 …

大数据的基础探索之大数据时代

前言&#xff1a;大数据已经是大势所趋&#xff0c;在这个网络时代能够不断地整合资源的人本身也是一种能力拥有者&#xff0c;在这个时代&#xff0c;如果一个人可以掌握数据分析工具&#xff0c;利用好云计算的能力&#xff0c;对于自己的个人而言来说都是一个极其重要的参与…

【PyTorch】张量(Tensor)的生成

PyTorch深度学习总结 第一章 Pytorch中张量(Tensor)的生成 文章目录 PyTorch深度学习总结一、什么是PyTorch&#xff1f;二、张量(Tensor)1、张量的数据类型2、张量生成和信息获取 总结 一、什么是PyTorch&#xff1f; PyTorch是一个开源的深度学习框架&#xff0c;基于Python…

20240212请问如何将B站下载的软字幕转换成为SRT格式?

20240212请问如何将B站下载的软字幕转换成为SRT格式&#xff1f; 2024/2/12 12:47 百度搜索&#xff1a;字幕 json 转 srt json srt https://blog.csdn.net/a_wh_white/article/details/120687363?share_token2640663e-f468-4737-9b55-73c808f5dcf0 https://blog.csdn.net/a_w…

上位机图像处理和嵌入式模块部署(利用python开发软件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 开发windows和linux软件的时候&#xff0c;大家一般都是习惯于用c/c语言进行开发&#xff0c;但是目前来说很多的开发板都是支持python语言开发的。…

RK3588平台开发系列讲解(视频篇)RKMedia 数据流向

文章目录 一、 获取RKMedia模块通道中的数据二、RKMedia的数据源和接收者三、模块通道绑定API调用 沉淀、分享、成长&#xff0c;让自己和他人都能有所收获&#xff01;&#x1f604; &#x1f4e2;RKMedia是RK提供的一种多媒体处理方案&#xff0c;可实现音视频捕获、音视频输…

服务治理中间件-Eureka

目录 简介 搭建Eureka服务 注册服务到Eureka 简介 Eureka是Spring团队开发的服务治理中间件&#xff0c;可以轻松在项目中&#xff0c;实现服务的注册与发现&#xff0c;相比于阿里巴巴的Nacos、Apache基金会的Zookeeper&#xff0c;更加契合Spring项目&#xff0c;缺点就是…

前端vue 数字 字符串 丢失精度问题

1.问题 后端返回的数据 是这样的 一个字符串类型的数据 前端要想显示这个 肯定需要使用Json.parse() 转换一下 但是 目前有一个问题 转换的确可以 showId:1206381711026823172 有一个这样的字段 转换了以后 发现 字段成了1206381711026823200 精度直接丢了 原本的数据…