算法导论---------动态规划之钢条切割

本文介绍了如何使用动态规划解决钢条切割问题,以最大化销售收益。动态规划的四个步骤被详细阐述,并通过一个具体的价格表样例展示了解决方案。文章分析了不同长度钢条的最优切割方案,展示了一般情况下如何用更短钢条的最优切割收益来描述Rn。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划方法通常用来求解最优化问题。动态规划算法设计步骤:

1.刻画一个最优解的结构特征。

2.递归定义最优解的值。

3.计算最优解的值,通常采用自底向上的方法。

4.利用计算出的信息构造一个最优解。

动态规划的实现方法:

带备忘的自顶向下法:此方法仍按自然的递归形式编写过程,但过程会保存每个子问题的解(通常保存在一个数组或散列表中)。当需要一个子问题的解时,过程首先检查是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;否则,按通常方式计算这个子问题。

<think>我们被要求解决钢条切割问题的动态规划方案,并在Excel中实现。钢条切割问题:给定一段长度为n英寸的钢条和一个价格表p_i(表示长度为i英寸的钢条的价格),求切割方案使得销售收益最大。动态规划思想:1.自底向上:我们先计算较小的子问题,然后逐步构建到较大的问题。2.定义:设r[j]表示长度为j英寸的钢条的最大收益。3.递推关系:r[j]=max(p_i+r[j-i]),其中i从1到j(如果j>=i),并且我们也可以不切割,即r[j]至少可以是p_j(如果j<=价格表的最大长度)。在Excel中实现,我们可以按照以下步骤:步骤1:准备两个区域-价格表:在某一列(如A列)输入长度(1,2,...,n),在相邻列(如B列)输入对应的价格。-动态规划表:在另一区域(如D列)放置长度(0,1,2,...,n),在E列放置对应长度的最大收益。步骤2:初始化-长度为0时,收益为0。所以D1=0,E1=0。步骤3:计算每个长度j的最大收益-对于每个长度j(从1到n),最大收益r[j]=max(p[i]+r[j-i]),其中i=1,2,...,j。注意:如果j大于价格表中最大长度,则p[i]只到价格表的最大长度,超过部分我们可以认为价格为0(或者只考虑i不超过j且价格表中存在的长度)。在Excel中,我们可以使用公式来实现。假设:-价格表在A2:B11(假设最大长度为10,A2:A11是长度1到10,B2:B11是对应价格)-动态规划表:D列是长度(0,1,...,n),E列是最大收益。D2=0,E2=0D3=1,要计算E3(即长度为1的最大收益)由于长度为1,只能切一段,所以最大收益就是p1(即B2)公式:E3=B2(因为j=1,只能取i=1,所以p1+r0=p1+0)D4=2,E4的计算:有两种选择:不切割(即取p2)或者切割成1+1(即p1+r1)所以E4=max(B3,//这里B3是长度2的价格(因为长度2在价格表中是第3行,A3=2,B3是价格)B2+E3//切割成1和1,即价格表中的1(B2)加上长度为1的最大收益(E3))但是注意,如果价格表中没有长度2,那么我们只能切割?所以这里我们假设价格表是完整的直到长度10。更一般地,对于长度j(在D列的第j+2行,因为D2是0,D3是1,所以长度j所在行是D(j+2)),最大收益E(j+2)的计算公式为:=MAX(如果j<=10(价格表最大长度),则考虑不切割(即价格表中长度为j的价格),然后考虑切割:对于i从1到j-1,计算p_i+r_{j-i}的最大值)但是Excel中不能直接写循环,我们可以使用一个数组公式,或者用MAX函数嵌套一个数组。另一种方法:我们可以用一个公式来考虑所有切割点。对于长度j,我们考虑第一段切i(i从1到j),那么收益为p_i+r_{j-i}。注意:p_i需要从价格表中获取,而r_{j-i}在动态规划表中已经计算。具体操作(以Excel公式为例):-假设我们在计算长度j(在单元格Dk,其中k=j+2)对应的最大收益(Ek)。-我们可以在Ek中写入公式:=MAX(如果价格表中有长度为j的价格,则取该价格;否则取一个很小的值(比如0),以及一个数组:对于i从1到j-1,计算:价格表中长度为i的价格+动态规划表中长度为j-i的收益)但是注意:我们无法在价格表中直接通过长度j来获取价格,因为价格表是按顺序排列的。我们可以用VLOOKUP函数。步骤:1.在动态规划表中,对于每个长度j(假设从第3行开始,第3行是长度1),在E3单元格输入公式:=MAX(VLOOKUP(D3,$A$2:$B$11,2,FALSE),//不切割,直接卖整段MAX(数组)//考虑切割,即第一段切1,2,...,j-1)但是注意:当j=1时,切割只能切1,而且j-1=0,所以切割部分不需要考虑(因为i从1到j-1是空集)。因此,我们只需要考虑不切割的情况。2.为了避免复杂的数组公式,我们可以借助辅助列。但这里我们可以用SUMPRODUCT或者直接写数组公式(按Ctrl+Shift+Enter)。另一种思路:不使用数组公式,而是用一列辅助计算。但动态规划表有n行,我们需要为每个长度j计算j次,这样就需要一个二维表?实际上,动态规划表是一维的,但计算过程中需要参考前面的结果。我们可以这样设计公式(数组公式):Ek=MAX(VLOOKUP(Dk,$A$2:$B$11,2,FALSE),//不切割MAX(OFFSET($E$2,1,0,1)+OFFSET($E$2,Dk-1,0,1)???)//这个不好表达更简单的方法:我们可以用如下公式(数组公式):=MAX(IF(ROW(INDIRECT("1:"&Dk))<=10,VLOOKUP(ROW(INDIRECT("1:"&Dk)),$A$2:$B$11,2,FALSE),0)+INDEX($E$2:$E$100,Dk-ROW(INDIRECT("1:"&Dk))+2,1))解释:ROW(INDIRECT("1:"&Dk))生成一个1到j的数组(j=Dk,即当前长度)。然后,对于每个i(从1到j):第一部分:如果i<=10,则从价格表中查i的价格,否则0(假设价格表只到10,且长度大于10的钢条没有价格,只能切割,但价格表只到10,所以超过10的部分我们无法直接卖整段,但切割后每段都不超过10,所以这里我们只考虑切割成不超过10的段,因此i最大取10,但j可能大于10?题目没有说明,我们假设价格表只到10,那么对于长度大于10的,只能切割成不超过10的段,所以我们在价格表中没有大于10的价格,那么不切割的选项就不存在(或者为0),所以我们可以不考虑j>10的不切割,因为价格表中没有j>10的价格,VLOOKUP会返回错误,我们可以在VLOOKUP外面用IFERROR处理,但为了简单,我们假设j不超过10(价格表最大长度)?或者我们允许j更大,那么不切割的选项就是0(因为没有价格),而切割部分我们只考虑i<=min(j,10),因为切出来的第一段长度i不能超过10(否则没有价格)。因此,修改公式:=MAX(IFERROR(VLOOKUP(Dk,$A$2:$B$11,2,FALSE),0),//不切割,如果Dk在价格表中有,则取价格,否则0MAX(IF(ROW(INDIRECT("1:"&MIN(Dk,10)))<=10,//只考虑1到min(j,10)VLOOKUP(ROW(INDIRECT("1:"&MIN(Dk,10))),$A$2:$B$11,2,FALSE)+INDEX($E$2:$E$100,Dk-ROW(INDIRECT("1:"&MIN(Dk,10)))+2,1))))注意:这个公式是数组公式,需要按Ctrl+Shift+Enter。但是,上面的公式中,INDEX($E$2:$E$100,...)用于获取长度为j-i的收益。其中,$E$2是长度0的收益,$E$3是长度1的收益,所以长度为x的收益在$E$2:$E$100的第x+1行(因为行2是长度0,行3是长度1,...,行k是长度k-2)。所以,对于长度j-i,它在动态规划表中的位置是:行号=(j-i)+2(因为D2是0,所以E2对应长度0,E3对应长度1,...,E(j-i+2)对应长度j-i)。所以,在公式中,对于每个i,我们需要取的行是:Dk-i+2(因为当前长度j=Dk,减去i后得到长度j-i,然后加上2是因为E2是第2行,长度0,所以长度j-i在E列的第(j-i)+2行)。注意:Dk是单元格的值,即当前长度j。因此,公式可以写成:=MAX(IFERROR(VLOOKUP(j,$A$2:$B$11,2,FALSE),0),MAX(数组:对于i从1到min(j,10):price(i)+r(j-i)))数组部分:用数组公式生成一个1到min(j,10)的序列,然后计算每个i对应的price(i)+r(j-i)的值,然后取最大值。具体公式(假设Dk是当前长度j,比如D3=1):=MAX(IFERROR(VLOOKUP(D3,$A$2:$B$11,2,FALSE),0),MAX(IF((ROW(INDIRECT("1:"&MIN(D3,10)))<=MIN(D3,10))*(ROW(INDIRECT("1:"&MIN(D3,10)))>0),VLOOKUP(ROW(INDIRECT("1:"&MIN(D3,10))),$A$2:$B$11,2,FALSE)+INDEX($E$2:$E$100,D3-ROW(INDIRECT("1:"&MIN(D3,10)))+2,1))))但是,这个公式中MAX函数不能直接作用于数组,所以我们需要用数组公式,按Ctrl+Shift+Enter,并且MAX函数只能有一个参数。我们可以用以下方式:=MAX(IFERROR(VLOOKUP(D3,$A$2:$B$11,2,FALSE),0),MAX(IF(ROW(INDIRECT("1:"&MIN(D3,10)))<=MIN(D3,10),VLOOKUP(ROW(INDIRECT("1:"&MIN(D3,10))),$A$2:$B$11,2,FALSE)+INDEX($E$2:$E$100,D3-ROW(INDIRECT("1:"&MIN(D3,10)))+2,1)))注意:这个公式需要按Ctrl+Shift+Enter,因为它包含数组操作。但是,当j=1时,min(1,10)=1,ROW(INDIRECT("1:1"))就是1,然后:VLOOKUP(1,...)得到价格1(假设在B2),INDEX($E$2:$E$100,1-1+2=2,1)就是E2(长度为0的收益,0)所以这一项就是B2+0,然后与不切割(也是B2)比较,所以取B2。当j=2时,min(2,10)=2,ROW(INDIRECT("1:2"))={1;2}i=1:price(1)+r(2-1)=B2+E3(因为2-1=1,在E3位置)i=2:price(2)+r(0)=B3+E2然后取这两个的最大值,再与不切割(B3)比较。但是注意:不切割就是i=j的情况,也就是我们数组中的i=j的情况(当j<=10时,数组中的i=j就是price(j)+r(0)=price(j)),所以不切割的情况已经包含在数组中了(当i=j时,j-i=0,所以r0=0)。所以我们可以省去不切割的单独判断,因为当i=j时,我们计算的就是price(j)+0。所以公式可以简化为:=MAX(IF(ROW(INDIRECT("1:"&MIN(Dk,10)))<=MIN(Dk,10),VLOOKUP(ROW(INDIRECT("1:"&MIN(Dk,10))),$A$2:$B$11,2,FALSE)+INDEX($E$2:$E$100,Dk-ROW(INDIRECT("1:"&MIN(Dk,10)))+2,1)))注意:这个公式需要按数组公式输入(Ctrl+Shift+Enter)。另外,当j=0时,我们不需要计算,因为初始化了E2=0。因此,在E3(长度1)的公式为(数组公式):{=MAX(IF(ROW(INDIRECT("1:"&MIN(D3,10)))<=MIN(D3,10),VLOOKUP(ROW(INDIRECT("1:"&MIN(D3,10))),$A$2:$B$11,2,FALSE)+INDEX($E$2:$E$100,D3-ROW(INDIRECT("1:"&MIN(D3,10)))+2,1)))}然后向下拖动填充到其他长度(直到长度n)。注意:当j大于价格表的最大长度(10)时,我们只考虑切割成1到10的段,因为大于10的段没有价格(或者我们也可以不切割,但价格表中没有,所以不切割收益为0,而切割成1到10的段至少会有收益,所以没问题)。步骤4:构建动态规划-D列:长度0,1,2,...,n(假设n=20)-E列:在E2输入0(长度0),在E3输入上面的数组公式(长度1),然后向下拖动到E22(长度20)。步骤5:如何记录切割方案?上面的公式只计算了最大收益,没有记录切割方案。为了记录方案,我们需要额外的列来记录第一段切割的长度(即最优解中第一段切多少)。在F列(方案列):对于长度j,我们想要知道在计算最大收益时,是哪个i使得收益最大(即第一段切i英寸)。我们可以用MATCH函数来找到在数组公式中哪个i对应的值等于最大收益(但是注意,可能有多个i得到相同的最大收益,我们取最小的i?或者任意一个,通常取第一个?但题目没有要求唯一,我们取任意一个即可)。在Fk(对应长度j)输入:=IF(Ek=0,0,//如果最大收益为0,则没有切割方案1//这里我们用一个简单方法:重新计算一次,找到第一个使得price(i)+r(j-i)=Ek的i)我们可以用:=MATCH(Ek,IF(ROW(INDIRECT("1:"&MIN(Dk,10)))<=MIN(Dk,10),VLOOKUP(ROW(INDIRECT("1:"&MIN(Dk,10))),$A$2:$B$11,2,FALSE)+INDEX($E$2:$E$100,Dk-ROW(INDIRECT("1:"&MIN(Dk,10)))+2,1),0)//精确匹配)这个公式也是数组公式(按Ctrl+Shift+Enter)。但是注意,由于浮点数计算可能产生误差,或者可能有多个相同的最大值,MATCH函数返回第一个匹配的位置(即i的值)。然后,我们可以根据第一段切割的长度i,然后剩下的长度j-i,再查看F列中长度为j-i的切割方案,这样递归地得到整个切割方案。但是,在Excel中递归地展开方案比较麻烦。我们可以用公式来拼接字符串?或者用VBA?这里我们只记录第一段切割的长度,然后用户可以根据剩下的长度去查找对应的方案。另一种方法:我们可以在每一行记录完整的最优切割方案(用字符串表示)。这需要更复杂的公式,我们可以用迭代计算,但Excel默认不开启迭代计算。我们可以用辅助列,但比较麻烦。为了简单,我们只记录第一段切割的长度,然后用户可以根据剩下的长度(j-i)在动态规划表中查找该长度对应的第一段切割长度,以此类推。例如:在G列,我们记录方案字符串。我们可以这样:G2:0->方案为空(长度为0)G3:如果F3=1,那么方案就是"1",否则如果F3>1,那么方案就是F3(字符串)加上剩余部分的方案。但是剩余部分的方案在G列中长度j-i的位置。所以,G3=IF(F3=0,"",F3&IF(D3-F3>0,","&INDEX($G$2:$G$100,D3-F3+2),""))然后向下填充。注意:这里D3-F3+2:因为D3是当前长度j,F3是第一段切去的长度i,那么剩余长度=j-i,这个剩余长度在动态规划表的第(j-i)+2行(因为行2是长度0,行3是长度1,...)。所以我们在G列的第(j-i)+2行取方案字符串。但是注意:这个公式是循环引用,因为G3可能依赖于G列后面的值?不对,我们是从小到大计算,所以当计算G3时,D3-F3一定小于D3,所以已经计算过了。所以不会循环引用。总结Excel实现步骤:1.在A列和B列输入价格表(A列长度,B列价格),从第2行开始(假设第1行是标题,或者从第2行开始是长度1),例如:A2:1,B2:1A3:2,B3:5...直到A11:10,B11:302.在D列输入长度0到n(例如D2=0,D3=1,D4=2,...直到D22=20)3.在E2输入0(长度为0的收益)4.在E3输入数组公式(按Ctrl+Shift+Enter):{=MAX(IF(ROW(INDIRECT("1:"&MIN(D3,10)))<=MIN(D3,10),VLOOKUP(ROW(INDIRECT("1:"&MIN(D3,10))),$A$2:$B$11,2,FALSE)+INDEX($E$2:$E$100,D3-ROW(INDIRECT("1:"&MIN(D3,10)))+2,1)))}然后向下拖动填充到E列其他单元格(直到长度n对应的行)。5.在F3输入数组公式(用于找到第一段切割长度):{=MATCH(E3,IF(ROW(INDIRECT("1:"&MIN(D3,10)))<=MIN(D3,10),VLOOKUP(ROW(INDIRECT("1:"&MIN(D3,10))),$A$2:$B$11,2,FALSE)+INDEX($E$2:$E$100,D3-ROW(INDIRECT("1:"&MIN(D3,10)))+2,1),0)}然后向下填充。6.在G2输入:""(长度为0的方案为空)在G3输入:=IF(F3=0,"",F3&IF(D3-F3>0,","&INDEX($G$2:$G$100,D3-F3+2),""))然后向下填充。注意:步骤5和6是可选的,如果只需要最大收益,那么步骤5和6可以省略。示例:假设价格表如下(长度1到10):长度:1,2,3,4,5,6,7,8,9,10价格:1,5,8,9,10,17,17,20,24,30然后我们计算长度为4:不切割:价格9切割方案:1+3:1+8=92+2:5+5=103+1:8+1=94:9最大收益为10(切割成2+2)。在Excel中,对于长度4(D5=4):E5=MAX(i=1:price(1)+r(3)->1+E4(长度3的收益)i=2:price(2)+r(2)->5+E4(长度2的收益)->注意:E4是长度3,E3是长度1,E4是长度2?不对,我们D列:D3=1,D4=2,D5=3,D6=4?这里要注意行号。我们重新设计动态规划表的位置:假设:D2:0->E2=0,F2=0,G2=""D3:1->E3,F3,G3D4:2D5:3D6:4所以,对于长度4在D6。在E6输入数组公式,计算:i=1:price(1)+r(3)->1+E5(因为D6=4,减去1得3,在D5,所以E5是长度3的收益)i=2:price(2)+r(2)->5+E4(长度2的收益)i=3:price(3)+r(1)->8+E3i=4:price(4)+r(0)->9+E2=9然后取最大值:max(1+E5,5+E4,8+E3,9)假设我们已经计算了:E3=1(长度1:只能不切割,1)E4:长度2:max(price(2)=5,price(1)+r(1)=1+1=2)->5E5:长度3:max(price(3)=8,price(1)+r(2)=1+5=6,price(2)+r(1)=5+1=6)->8所以E6=max(1+8,5+5,8+1,9)=max(9,10,9,9)=10然后在F6:用MATCH函数,在数组[1+8=9,5+5=10,8+1=9,9]中找10,位置是2(第二个,即i=2)所以F6=2然后G6:=2&IF(4-2>0,","&INDEX(G列,4-2+2行),"")4-2=2,所以INDEX(G列,2+2=4行)->G4(长度2的切割方案)长度2(D4)的切割方案:在G4中,计算F4:长度2的F4是多少?对于长度2(D4=2):E4=5(上面计算)F4:用MATCH找,数组:i=1:price(1)+r(1)=1+1=2;i=2:price(2)=5->最大值5,位置2(因为数组[2,5]中5是第二个)所以F4=2G4:=2&IF(2-2>0,...)->因为2-2=0,所以只输出"2"所以G6="2,"&"2"->"2,2"因此,我们得到长度为4的最优切割方案是2,2。注意:如果价格表不是递增的,可能切割方案有多种,我们只取第一个达到最大值的切割点。以上就是用Excel实现钢条切割问题的动态规划方法。注意:如果n很大(比如100),那么数组公式会生成一个长度为min(100,10)=10的数组,所以计算量不大。但是,如果价格表的最大长度不是10,而是L,那么数组长度就是min(j,L),所以当L很大且j很大时,数组会很大,可能影响性能。不过通常L不会太大。最后,我们提供示例Excel表格的截图或描述是不现实的,所以用文字描述步骤。另外,如果用户需要,我们可以提供一个示例Excel文件,但这里只能描述步骤。参考文献:[^1]:钢条切割(dp解法)[^2]:动态规划----钢条切割问题[^3]:钢条切割(记忆型递归)dp[^4]:算法导论--动态规划钢条切割)[^5]:算法很美笔记(Java)——动态规划</think>### 钢条切割问题的动态规划解决方案及Excel实现 钢条切割问题是一个经典的动态规划问题:给定长度为 $n$ 英寸的钢条和价格表 $p_i$(长度为 $i$ 英寸的价格),求最大化销售收益的切割方案。动态规划通过存储子问题解避免重复计算,核心递推关系为: $$r_j = \max_{1 \leq i \leq \min(j,m)} \left( p_i + r_{j-i} \right)$$ 其中 $r_j$ 表示长度 $j$ 的最大收益,$m$ 是价格表最大长度。 --- ### **Excel实现步骤** **假设价格表(A1:B11):** | 长度(i) | 价格(p_i) | |---------|----------| | 1 | 1 | | 2 | 5 | | ... | ... | | 10 | 30 | **动态规划表(D1:Gn):** | 长度(j) | 最大收益(r_j) | 第一段长度 | 完整方案 | |---------|--------------|------------|----------| | 0 | 0 | - | "" | | 1 | =计算 | =计算 | =拼接 | | ... | ... | ... | ... | #### **步骤详解:** 1. **初始化(D2:G2):** - D2: `0`(长度) - E2: `0`(收益) - F2: `""`(方案) - G2: `""`(完整方案) 2. **计算最大收益(E列,数组公式需Ctrl+Shift+Enter):** - **E3公式**(长度1): ```excel =MAX( IFERROR(VLOOKUP(D3, $A$2:$B$11, 2, FALSE), 0), IF(ROW(INDIRECT("1:" & MIN(D3, 10))) <= MIN(D3, 10), VLOOKUP(ROW(INDIRECT("1:" & MIN(D3, 10))), $A$2:$B$11, 2, FALSE) + INDEX($E$2:$E$100, D3 - ROW(INDIRECT("1:" & MIN(D3, 10))) + 2, 1) ) ``` - **向下填充**至长度 $n$。 3. **记录第一段切割长度(F列,数组公式):** - **F3公式**: ```excel =MATCH(E3, IF(ROW(INDIRECT("1:" & MIN(D3, 10))) <= MIN(D3, 10), VLOOKUP(ROW(INDIRECT("1:" & MIN(D3, 10))), $A$2:$B$11, 2, FALSE) + INDEX($E$2:$E$100, D3 - ROW(INDIRECT("1:" & MIN(D3, 10))) + 2, 1), 0) ``` 4. **生成完整方案(G列):** - **G3公式**: ```excel =IF(F3="", "", F3 & IF(D3-F3>0, "," & INDEX($G$2:$G$100, D3-F3+2), "") ) ``` --- ### **示例验证(长度4)** | 长度(j) | 收益(r_j) | 第一段 | 方案 | |---------|----------|--------|-------| | 1 | 1 | 1 | "1" | | 2 | 5 | 2 | "2" | | 3 | 8 | 3 | "3" | | **4** | **10** | **2** | **"2,2"** | - **计算过程**: - $r_4 = \max(p_1 + r_3=9,\ p_2 + r_2=10,\ p_3 + r_1=9,\ p_4=9) = 10$ - 最优方案:切割为2英寸+2英寸[^2][^4]。 --- ### **动态规划的优势** - **时间优化**:自底向上计算,避免递归重复(如直接递归复杂度 $O(2^n)$,DP为 $O(n^2)$)[^4][^5]。 - **空间优化**:仅需一维数组存储子问题解[^1][^3]。 --- ### **注意事项** 1. 价格表需覆盖所有 $i \leq m$($m$ 为最大分段长度)。 2. 数组公式必须按 **Ctrl+Shift+Enter** 生效。 3. 若 $n > m$,公式自动限制 $i \leq m$ 保证有效性。 通过Excel实现,可直观验证动态规划的子问题依赖和最优解构造过程[^2][^4]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值