DP问题.docx
- 文档编号:5962824
- 上传时间:2023-01-02
- 格式:DOCX
- 页数:17
- 大小:49.19KB
DP问题.docx
《DP问题.docx》由会员分享,可在线阅读,更多相关《DP问题.docx(17页珍藏版)》请在冰豆网上搜索。
DP问题
动态规划
动态规划是解决最优化问题的最有效算法。
最优化问题可能有许多种可行解。
每个解都有一个值,而我们希望找到一个具有最优(最大或最小)值的解。
称这样的问题为该问题的“一个”最优解(而不是“确定的”最优解),因为可能存在多个最优解的值。
动态规划的基本思想:
是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。
分解得到的子问题数目太多,有些子问题被重复计算了很多次。
如果我们能够保存已解决的子问题的结果,在需要时再利用这些结果,这样就可以避免大量的重复计算,节省时间。
通常可以用一个表来记录所有已解的子问题的答案。
不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。
这就是动态规划法的基本思路。
具体的动态规划算法多种多样,但它们具有相同的填表格式。
***动态规划是用空间换时间的一种方法的抽象。
其关键是发现子问题和记录其结果。
然后利用这些结果减轻运算量。
动态规划实质:
枚举+递推
枚举:
列举状态递推:
状态转移方程
设计动态规划法的步骤:
设计一个标准的动态规划算法,通常可按以下几个步骤进行:
划分阶段:
按照问题的时间或空间特征,把问题分为若干个阶段。
注意这若干个阶段一定要是有序的或者是可排序的(即无后向性),否则问题就无法用动态规划求解。
选择状态:
将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。
当然,状态的选择要满足无后效性。
确定决策并写出状态转移方程:
之所以把这两步放在一起,是因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。
所以,如果我们确定了决策,状态转移方程也就写出来了。
但事实上,我们常常是反过来做,根据相邻两段的各状态之间的关系来确定决策。
写出规划方程(包括边界条件):
动态规划的基本方程是规划方程的通用形式化表达式。
一般说来,只要阶段、状态、决策和状态转移确定了,这一步还是比较简单的。
动态规划问题的特征
动态规划算法的有效性依赖于问题本身所具有的两个重要性质:
最优子结构性质和子问题重叠性质。
1、最优子结构:
当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
2、重叠子问题:
在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。
动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。
【题1】数字三角形
2
62
184
1568
……
图1—1数字三角形
给定一个具有N层的数字三角形,从顶至底有多条路径,每一步可沿左斜线向下或沿右斜线向下,路径所经过的数字之和为路径得分,请求出最小路径得分。
【解题思路】这道题可以用动态规划成功地解决,但是,如果对问题的最优结构刻画得不恰当(即状态表示不合适),则无法使用动态规划。
状态表示法一:
用一维数组D(X)描述问题,D(X)表示从顶层到达第X层的最小路径。
因此,此问题就是求出D(N)(若需要,还应求出最优路径)。
这是一种很自然的想法和表示方法。
遗憾的是,这种描述方式并不能满足最优子结构性质。
因为D(X)的最优解(即最优路径)可能不包含子问题例如D(X-1)的最优解。
显然,D(4)=2+6+1+1=10,其最优解(路径)为2-6-1-1
而D(3)=2+2+4=8,最优解(路径)为2-2-4。
故D(4)的最优解不包含子问题D(3)的最优解。
由于不满足最优子结构性质,因而无法建立子问题最优值之间的递归关系,也即无法使用动态规划。
状态表示法二:
用二维数组D(X,y)描述问题,D(X,y)表示从顶层到达第X层第y个位置的最小路径得分。
最优子结构性质:
容易看出,D(X,y)的最优路径Path(X,y)一定包含子问题D(X-1,y)或D(X-1,y-1)的最优路径。
否则,取D(X-1,y)和D(X-l,y-1)的最优路径中得分小的那条路径加上第X层第y个位置构成的路径得分必然小于Path(X,y)的得分,这与Path(X,y)的最优性是矛盾的。
因为每次只能向左下或右下走,所以,一旦D(x,y)确定了,那么显然,下一步的时候,只跟现在这个位置有关,不关心前面是怎么过来的,这也就满足了无后效性。
如图:
D(4,2)的最优路径为2-6-1-5,它包含D(3,1)最优路径2-6-1。
因此,用二维数组D(X,y)描述的计算D(X,y)的问题具有最优子结构性质。
递归关系:
D(X,y)=min{D(X-1,y),D(X-1,y-1}+a(X,y)
D(1,1)=a(1,1)
其中,a(X,y)为第X层第y个位置的数值。
原问题的最小路径得分可以通过比较D(N,i)获得,其中i=1,2,…,N。
在上述递归关系中,求D(X,y)的时候,先计算D(X-1,y)和D(X-1,y-1),下一步求D(X,y+1)时需要D(X-1,y+1)和D(X-1,y),但其中D(X-1,y)在前面已经计算过了。
于是,子问题重叠性质成立。
因此,采用状态表示法二描述的问题具备了用动态规划求解的基本要素,可以用动态规划进行求解。
状态表示法三:
采用状态表示法二的方法是从顶层开始,逐步向下至底层来求出原问题的解。
事实上,还可以从相反的方向考虑。
仍用二维数组D(X,y)描述问题,D(X,y)表示从第X层第y个位置到达底层的最小路径得分。
原问题的最小路径得分即为D(1,1)。
D(1,1)的最优路径为2-6-1-1,它包含D(2,1)的最优路径6-1-1。
因此,这种状态表示描述的计算D(X,y)的问题同样具有最优子结构性质。
动态方程:
D(X,y)=min{D(X+1,y),D(X+1,y+1)}+a(X,y)
D(N,k)=a(N,k),k=1,…,N{最下面一层}
其中,a(X,y)为第X层第y个位置的数值。
D(X,y)表示从第X层第y个位置到达底层的最小路径得分。
原问题的最小路径得分即为D(1,1)
采用状态表示法三的算法的主要过程如下:
fori:
=n-2downto0do//从最下层开始,计算每个x,y位置的元素到最后一行的最小值
begin
forj:
=0toido//计算每一行的每一个数字
begin
/*j在变化,j每循环完次都得到一个这一行到最底行最小的位置,同时也记录了每个位置到最底行的值;这里的if是在找每个位置的下两步路线中较小的一个*/
tmp:
=sou[i+1,j];
if(sou[i+1,j+1] tmp: =sou[i+1,j+1]; sou[i,j]: =s[i,j]+tmp; write(sou[0,0]); 这是一道非常典型的DP,因为它的子问题实在是太多了,所以将问题的结果保存起来,自底向上的递推,这个思想就是动态规划 【递归算法】 varn,i,j: integer; a: array[1..100,1..100]ofinteger; functionsolve(x,y: integer): integer; varu,v: integer; begin ifx=nthenbeginsolve: =a[x,y];exit;end; u: =solve(x+1,y); v: =solve(x+1,y+1); ifu>vthensolve: =a[x,y]+uelsesolve: =a[x,y]+v; writeln(x,'',y,')',a[x,y]); writeln('',u,'',v); end; begin read(n); fori: =1tondo forj: =1toidoread(a[i,j]); writeln(solve(1,1)); readln; end. 动态规划与搜索——动态规划是高效率、高消费算法 同样是解决最优化问题,有的题目我们采用动态规划,而有的题目我们则需要用搜索。 这其中有没有什么规则呢? 我们知道,撇开时空效率的因素不谈,在解决最优化问题的算法中,搜索可以说是“万能”的。 所以动态规划可以解决的问题,搜索也一定可以解决。 把一个动态规划算法改写成搜索是非常方便的,状态转移方程、规划方程以及边界条件都可以直接“移植”,所不同的只是求解顺序。 动态规划是自底向上的递推求解,而搜索则是自顶向下的递归求解(这里指深度搜索,宽度搜索类似)。 反过来,我们也可以把搜索算法改写成动态规划。 状态空间搜索实际上是对隐式图中的点进行枚举,这种枚举是自顶向下的。 如果把枚举的顺序反过来,变成自底向上,那么就成了动态规划。 (当然这里有个条件,即隐式图中的点是可排序的,详见下一节。 ) 正因为动态规划和搜索有着求解顺序上的不同,这也造成了它们时间效率上的差别。 在搜索中,往往会出现下面的情况: 对于上图(a)这样几个状态构成的一个隐式图,用搜索算法就会出现重复,如上图(b)所示,状态C2被搜索了两次。 在深度搜索中,这样的重复会引起以C2为根整个的整个子搜索树的重复搜索;在宽度搜索中,虽然这样的重复可以立即被排除,但是其时间代价也是不小的。 而动态规划就没有这个问题,如上图(c)所示。 一般说来,动态规划算法在时间效率上的优势是搜索无法比拟的。 (当然对于某些题目,根本不会出现状态的重复,这样搜索和动态规划的速度就没有差别了。 )而从理论上讲,任何拓扑有序(现实中这个条件常常可以满足)的图中的搜索算法都可以改写成动态规划。 但事实上,在很多情况下我们仍然不得不采用搜索算法。 动态规划总要遍历所有的状态,而搜索可以排除一些无效状态。 更重要的事搜索还可以剪枝,可能剪去大量不必要的状态,因此在空间开销上往往比动态规划要低很多。 如何协调好动态规划的高效率与高消费之间的矛盾呢? 有一种折衷的办法就是记忆化算法。 记忆化算法在求解的时候还是按着自顶向下的顺序,但是每求解一个状态,就将它的解保存下来,以后再次遇到这个状态的时候,就不必重新求解了。 这种方法综合了搜索和动态规划两方面的优点,因而还是很有实用价值的。 【题2】0-1背包问题 给定N中物品和一个背包。 物品i的重量是Wi,其价值Pi ,背包的容量为C。 问应该如何选择装入背包的物品,使得转入背包的物品的总价值为最大? ? 在选择物品的时候,对每种物品i只有两种选择,即装入背包或不装入背包。 不能讲物品i装入多次,也不能只装入物品的一部分。 因此,该问题被称为0-1背包问题。 问题分析: 01背包的状态转换方程 f[i,j]=Max{f[i-1,j-Wi]+Pi, f[i-1,j]}(j>=Wi) f[i,j]表示在前i件物品中选择若干件放在承重为j的背包中,可以取得的最大价值。 Pi表示第i件物品的价值。 决策: 为了背包中物品总价值最大化,第i件物品应该放入背包中吗? 可以压缩空间,f[v]=max{f[v],f[v-w[i]]+p[i]} 举例: 有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和? name weight value 1 2 3 4 5 6 7 8 9 10 a 2 6 0 6 6 9 9 12 12 15 15 15 b 2 3 0 3 3 6 6 9 9 9 10 11 c 6 5 0 0 0 6 6 6 6 6 10 11 d 5 4 0 0 0 6 6 6 6 6 10 10 e 4 6 0 0 0 6 6 6 6 6 6 6 通过手工填写上表,理解01背包的动态规划算法。 该表是至底向上,从左到右生成的。 为了叙述方便,用e2单元格表示e行2列的单元格,这个单元格的意义是用来表示只有物品e时,有个承重为2的背包,那么这个背包的最大价值是0,因为e物品的重量是4,背包装不了。 对于d2单元格,表示只有物品e,d时,承重为2的背包,所能装入的最大价值,仍然是0,因为物品e,d都不是这个背包能装的。 同理,c2=0,b2=3,a2=6。 对于承重为8的背包,a8=15,是怎么得出的呢? 根据01背包的状态转换方程,需要考察两个值, 一个是f[i-1,j],对于这个例子来说就是b8的值9,另一个是f[i-1,j-Wi]+Pi; f[i-1,j]表示有一个承重为8的背包,当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值 f[i-1,j-Wi]表示我有一个承重为6的背包(等于当前背包承重减去物品a的重量),当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值 f[i-1,j-Wi]就是指单元格b6,值为9,Pi指的是a物品的价值,即6 由于f[i-1,j-Wi]+Pi=9+6=15大于f[i-1,j]=9,所以物品a应该放入承重为8的背包 程序一: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var i,j,v,n: longint; f,c,w: array[0..100] of longint; function max(a,b: longint): longint; begin if a>b then exit(a) else exit(b); end; begin read(n,v); fillchar(f,sizeof(f),0); for i: =1 to n do read(w[i],p[i]); for i: =1 to n do for j: =v downto w[i] do{downto0也可以} f[j]: =max(f[j],f[j-w[i]]+p[i]); writeln(f[v]); end. 程序二(顺推法): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var m,n,x,i: integer; c,w: array[1..30] of integer; f: array[0..30,0..300] of integer; function max(x,y: integer): integer; begin if x>y then max: =x else max: =y; end; begin readln(n,m); for i: =1 to n do readln(w[i],p[i]); for i: =1 to n do for x: =1 to m do if x>=w[i] then f[i,x]: =max(f[i-1,x-w[i]]+p[i],f[i-1,x]) else f[i,x]: =f[i-1,x]; writeln(f[n,m]); end. 【题3】装箱问题 有一个箱子容量为V(正整数,0≤V≤20000),同时有n个物品(0 要求从n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。 [样例]输入: 24一个整数,表示箱子容量 6一个整数,表示有n个物品 8接下来n行,分别表示这n个物品的各自体积。 3 12 7 9 7 输出: 0一个整数,表示箱子剩余空间。 使用动态程序设计方法计算箱子的最小剩余空间 如果按照物品序号依次考虑装箱顺序的话,则问题具有明显的阶段特征。 问题是当前阶段的剩余空间最小,并不意味下一阶段的剩余空间也一定最小,即该问题并不具备最优子结构的特征。 如果以装箱的体积作为状态,则阶段间的状态转移关系顺其自然,可使得最优化问题变为判定性问题。 设状态转移方程 f[i,j]——在前i个物品中选择若干个物品(包括i)装箱,其体积正好为j的标志。 显然f[i,j]=f[i-1,j-box[i]],即物品i装入箱子后的体积正好为j的前提是f[i-1,j-box[i]]=true。 初始时,f[0,0]=true(1≤i≤n,box[i]≤j≤v)。 由f[i,j]=f[i-1,j-box[i]]可以看出,当前阶段的状态转移方程仅与上一阶段的状态转移方程有关。 因此设f0为i-1阶段的状态转移方程,f1为i阶段的状态转移方程,这样可以将二维数组简化成一维数组。 我们按照下述方法计算状态转移方程f1: fillchar(f0,sizeof(f0),0);{装箱前,状态转移方程初始化} f0[0]←true; fori←1tondo{阶段i: 按照物品数递增的顺序考虑装箱情况} begin f1←f0;{i阶段的状态转移方程初始化} forj←box[i]tovdo{状态j: 枚举所有可能的装箱体积} iff0[j-box[i]]thenf1[j]←true; {若物品i装入箱子后的体积正好为j,则物品i装入箱子} f0←f1;{记下当前装箱情况} end;{for} 经过上述运算,最优化问题转化为判定性问题。 再借用动态程序设计的思想,计算装箱的最大体积k= 。 显然最小剩余空间为v-k: fori←vdownto0do{按照递减顺序枚举所有可能的体积} iff1[i]thenbegin{若能装入体积为i的物品,则输出剩余空间v-i,退出程序} writeln(v-i);halt end;{then} end.{for} writeln(v);{在未装入一个物品的情况下输出箱子体积} 采用穷举法,用一个B数组来表示取数的标记,当B=0时表示第i件物品不取,当B=1时表示第i件物品已取,初始化全部取0,以下算法是从后面的物品开始取起,通过B数组的取值把15种取法全部穷举出来,价值MAX初始化为0。 生成B数组中数据的方法如下: fillchar(b,sizeof(b),0); whileb[0]=0dobegin j: =n; whileb[j]=1dodec(j); b[j]: =1; fori: =j+1tondob: =0; end; 【题4】花束摆放 现有F束不同品种的花束,同时有至少同样数量的花瓶被按顺序摆成一行,其位置固定于架子上,并从1至V按从左到右顺序编号,V是花瓶的数目(F≤V)。 花束可以移动,并且每束花用1至F的整数唯一标识。 标识花束的整数决定了花束在花瓶中排列的顺序,如果i 每个花瓶只能放一束花。 如果花瓶的数目大于花束的数目,则多余的花瓶空置。 每一个花瓶都具有各自的特点。 因此,当各个花瓶中放入不同的花束时,会产生不同的美学效果,并以一美学值(一个整数)来表示,空置花瓶的美学值为零。 为取得最佳美学效果,必须在保持花束顺序的前提下,使花束的摆放取得最大的美学值。 请求出具有最大美学值的一种摆放方式。 【输入格式】第一行包含两个数: F,V。 (1<=F<=100,F<=V<=100) 接下来的F行: 包含V个数,输入Aij矩阵。 (-50<=Aij<=50,Aij为第i束花放在第j个花瓶中的美学值) 【输出格式】第一行输出最大美学值第二行给出方案 【输入样例】 35 723-5-2416 521-41023 -215-4-2020 【输出样例】53 245 请输入花瓶数目V(V>=F&&V<=40): 5 请输入花束数目F(F<=V&&F<=30): 3 请输入花束对应花瓶的美学值(>0): 第1束花放在第1到第5个花瓶中的美学值: 564912 第2束花放在第1到第5个花瓶中的美学值: 1435820、 第3束花放在第1到第5个花瓶中的美学值: 2020111210 解题思路 v状态表示法一: 设A(i,j)表示第i种花束摆在第j个花瓶中获得的美学值。 S(i,k)表示第i种花束摆在第k个花瓶中时(这里k≥i),前i种花束能够获得的最大美学值(之和)。 这样,原问题的最优值可以通过计算max{S(F,k)F≤k≤V}获得。 这种状态表示法描述问题的方式是否具备了用动态规划求解的基本要素? 最优子结构性质: 对满足F≤k≤V的k,设T(F,k)是达到最优值S(F,k)的一种最佳摆放方式,其中,第F-1种花束摆在第j个花瓶中(j 故对每个满足F≤k≤V的k,计算S(F,k)的问题具有最优子结构性质。 动态方程: S(i,k)=max{S(i-1,j)i-1≤j≤k-1}+A(i,k)i>1 每走一步,计算出了所有i的花束摆放在所有可能的位置之后的所有值。 最后,递推出了s(F,*)的值取最大既得到结果。 S(1,k)=A(1,k) 第i-1个花摆在那里之后,下一步怎么摆,只跟现在花在哪里有关,而与前面的摆放顺序无关。 在计算S(i,k-1)时,已经计算出了S(i-1,j),i-1≤j≤k-2及其max{S(i-1,j)i-1≤j≤k-2}。 因此,计算S(i,k)时,只要将S(i-1,k-1)与max{S(i-1,j)i-1≤j≤k-2}进行比较即可求得,即子问题重叠性质。 这样做可以大大减少计算量,不用每次都去一一比较。 类似0/1背包问题。 a[i,j]=花i放入瓶j的美学价值s[i,j]=前i束花放入前j个花瓶内的最大美学价值决策: 花i放不放在第j个瓶中。 (1)放: s[i,j]=s[i-1,j-1]+a[i,j] (2)不放: s[i,j]=s[i,j-1]取s[i,j]=max{s[i-1,j-1]+a[i,j],s[i,j-1]} 状态表示法二: 设S[i,k]表示第i种花束摆在第k个之前(包括第k个)的任意某个花瓶中,前i种花束能够获得的最大美学值(之和)。 这样,原问题的最优值即为S[F,V]。 这比前一个表示法更直接。 其递归方程为: S[i,k]=max{S[i-1,k-1]+A(i,k),S[i,k-1]}(i>1,k>i); 初始条件为: S[1,1]=A[1,1]; S[1,k]=max{A(1,k),S[1,k-1]},(k>1); S[i,i]=S[i-1,i-1]+A(i,i),(i>1) vara,f: array[0..100,0..100]oflongint; i,j,k,l,m,n,r,t,s,x,y: longint; ch: ansistring; b: array[0..101,0..101]ofansistring; beginreadln(n,k); fori: =1tondo forj: =1tokdoread(a[i,j]); fori: =1tondobegin t: =0;for
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- DP 问题