动态规划总结
写在前面的话:
1、本总结的题目均出自vijos动态规划分类和其他著名题库。每道例题都添加了对应的vijos题目的地址链接,可以通过ctrl+单击进入题目。读者在阅读本文以后可以将这些练习题温习以充实提高。
2、本文以例题为主,一些具体定义可能会有所偏差,但并不影响算法的正确性。文章最后给出了一些完整定义,如果有兴趣可以阅读。
3、如果本文有所疏漏,请与作者联系(QQ:397740194)。本人将不胜感激。
动态规划小引:
算法:动态规划是记忆化搜索的循环结构形式(枚举子结构)。动态规划的优势是思路清晰,调试便捷并且效率比一般搜索要高(加强剪枝除外)。
特征:动态规划的思路是由初始状态通过状态转移达到目标状态。这便意味着可以运用动态规划解决的问题必须具备以下特征:
(1) 后面的状态不会对前面的状态产生影响,即无后效性。
(2) 局部最优会通过转移使全局达到最优,即最优子结构。
区别于贪心法:运用动态规划解决的问题由于具有最优子结构,通常会误用贪心法解决。实际上,贪心法和动态规划的主要区别就是状态转移。贪心法是不需要状态转移的,因为它的每一步的最优结果都必然导致最终结果的最优。然而动态规划的精髓就是状态转移方程。有了方程,实现程序便是小菜一碟了。
第一节 线性动态规划
一、极值问题
1、背包问题:
想必大家最一开始接触动态规划都是从01背包开始的。
例题1:01背包问题:
给出N个物品的体积和价值,用有限包的容量装最大价值的物品。
对于当前状态,可以用两个变量表示:[I,num](i代表第i个物品,num代表使用的容量)。状态转移就通过每个背包来进行。因为每个背包只有两种情况:使用或不使用。因此我们得出dp[I,num]的转移来源:dp[i-1,num]和dp[i-1,num-v[i]](v[i]代表第i个物品的体积)。从而得出转移方程:
max( dp[i-1,j] , dp[I,j-v[i]] ) j>=v[i]
Dp[I,j]:=
Dp[i-1,j] j< v[i]
Dp[0,0]:=0;
可以看出,01背包是通过一个约束条件得出最大值。而广义的背包问题的约束条件可以不止1个约束条件。
例题2:NASA的食物计划
这道题就是通过对体积和质量这两个条件的约束,要求出最大值。方程和01背包问题很类似:
Dp[I,j,k]:=max(dp[i-1,j,k],dp[i-1,j-v[i],k-m[i]]+calori[i]); j是体积,k是质量
边界条件和01背包问题基本一致。
*通过对以上两题的总结,我们得出了一类背包问题的通解:
Dp[i,j1,j2...jm]=
Max |
dp[i-1,j1,j2...jm]
dp[i-1,j1-v1[i],j2-v2[i]...jm-vm[i]]+num[i]
在某些情况下,题目要求输出最小值,那么将max改为min即可。
2、常见简单一维动态规划问题:
例题4:导弹拦截(第一问)
这道题第一问就是经典的“最长不升子序列”问题。我们考虑,对于到当前一位的最长的不升子序列,只能通过之前一个数值大于等于他的那一位的最大结果值加一得到。于是我们得出方程:
Dp[i]:=max(dp[j]+1)(1<=j<i and num[j]>=num[i])
*此类问题均是一维线性dp,十分简单,方程可以通过简单的分析或者找规律求得。
3、旅行问题:
这类问题通常需要求从一个点走到另一个点的最大、小值。通常情况下,这类问题是有分层结构的,因此这种情况下运用动态规划解决要比求最短路思路更为清晰。
例题5:小胖办证
这道题是求用最少耗费在矩阵中从一点到另一点。对于每个点,来源至多有3种:左、右、上。因此状态转移有三种。对于当前状态:第i行,第j列,有(i-1,j),(I,j-1),(I,j+1)三种前驱。因此写出方程
Dp[I,j]:=min(dp[i-1,j],dp[I,j-1],dp[I,j+1])+num[I,j]
需要指出的是,对于同层转移,由于方向不同,导致具有后效性,但是单独一个方向仍然满足动态规划的特征,因此我们需要进行两次方向不同的dp,并取最小值即可。
例6:旅行商简化版
由于这道题的旅行路线具有方向性,因此我们就利用x坐标的单调性来求解。我们用dp[I,j]存储第一个人走到I,第二个人走到j的最短路程和。显然要i>j。当然我们要预处理dist[I,j]为i到j的坐标距离,即sqrt(sqr(xi-xj)+sqr(yi-yj))。
易见dp[I,1]=dp[i-1,1]+dist[i-1,j]。可以进一步求得当j<i-1时dp[I,j]=dp[i-1,j]+dist[i-1,i]。
当i=j+1的时候,我们可以想象成将第一个人固定在i-1处,第二个人从k走到i,然后两人互换位置。于是:
Dp[I,j-1]:=max(dp[i-1,k]+dist[k,i])。(1<=k<j)
最后的答案就是dp[n,n-1]+dist[n-1.n]。
例题7:Canada Tour(USACO Chapter5)
关于这道题的解答,在网上遍地都是,我就不再卖弄Rp了。^.^
例题8:天堂的馈赠
这道题的题意不好理解。在彻底理解题意的前提下,我们仍然不容易想到将它转化为取数类Dp。不过,我们可以换一种思路去考虑。我们可以做出一个大表,横轴是天堂的宽度,纵轴是时间。在时间T的格子存放的是T时刻掉到底部的物品的价值。于是我们从第一行的第p格出发取数,直到走到第T行为止。求第T行的最大值即可。
4、最大子图案类问题。
这种问题多是求在一个图形里面的满足条件的子图形,有时候要求最大面积,有时候仅仅是判断是否存在。这类问题都可以用动态规划轻易地解决。
例题9:盖房子
这道题很简单(可与一维背包相比)。对于第i行,第j列的点,存储dp[I,j]为以该点向左上方所做的最大的正方形的边长。
Dp[i-1,j]
Dp[I,j-1]
容易看出,
dp[I,j]=min(dp[i-1,j],dp[I,j-1])+1([i,j]满足要求)
dp[i,j]=1 ([i,j]不满足要求)
最后输出dp[I,j]中的最大值即可。
例题10:迎春舞会之集体舞
这道题和盖房子很类似,我们可以利用三角形的特点来解决。由于三角形图案的两种形式具有对称性,我就先对向下的三角形进行处理,向上的三角形的情况读者可以同理解决。
我们考虑,对于每一个向下的三角形点[i,j],存储以它为顶点构成的最大的向下三角形dp[i,j]。
可以发现,当[i,j]不为’-’;或者[i-1,j]不为’-’时,最大的边长为1。
否则dp[i,j]=min(dp[i-1,j-1],dp[i-1,j+1])+1。
二、可行性问题
此类问题多用bollean存储结果,因而空间复杂度较高,然而此类问题若不用动态规划只能用搜索做,不易剪枝,因此时间复杂度相对要小很多。
此类问题主要包括积木城堡、新年趣事之打牌、猫狗大战、邮票面值设计、
这类问题相当简单,我就不浪费字节了。嘿嘿 *.^
第二节 树状动态规划
一、树状dp的动机:
当数据结构不再是理想中的线性的,而是树状的时候,我们不愿去进行低效的搜索,希望用记忆化来帮助优化时间效率。于是-----树状DP诞生了……
二、树状dp的运算方式:
树状dp采用dfs模式,从root出发来进行运算。
三、树状dp的程序模式:
Procedure dfs(v:integer);
Var
I:integer;
Begin
For i:=1 to n do if father[i]=v then
Begin
Dfs(i);
Dp[v]çFunc(dp[i]);
End;
End;
四、树状dp例题讲解
例题11:小胖守皇宫
考虑这道题对于一个节点I的最小值dp[i],必然由i的子节点进行控制。而每个点向下只有三种情况:这个点设置守卫、这个点不设置守卫但下面有一个点控制这个点、这个点不设置守卫且下面没有点控制这个点。对于这三种状态,我们分别存为1,2,3。
对于dp[I,1],我们只要找子节点的最小值的和即可。
对于dp[I,3],我们只要求子节点中状态1的和即可。
重要的是dp[I,2],因为这个点如果不设置守卫,只需要在子节点中设置一个守卫就足够了。于是我们先求子节点中状态2、3最小值的和,然后再找子节点中状态2到状态1的最小增量(delta)即可。
给出程序的核心部分:
procedure dfs(v:longint);
var
i,s1,s2,s3,minn:longint;
leaf:boolean;
begin
s1:=0;s2:=0;s3:=0;leaf:=true;minn:=maxint;
for i:=1 to n do
if fa[i]=v then
begin
leaf:=false;
dfs(i);
s1:=s1+dp[i,2];
s2:=s2+min(dp[i,3],dp[i,2]);
s3:=s3+min(min(dp[i,1],dp[i,2]),dp[i,3]);
minn:=min(minn,dp[i,3]-min(dp[i,2],dp[i,3]));
end;
if leaf then
begin
if fa[v]<>0 then dp[v,1]:=0
else dp[v,1]:=maxint;
dp[v,2]:=maxint;
dp[v,3]:=num[v];
end
else begin
dp[v,1]:=s1;
dp[v,2]:=s2+minn;
dp[v,3]:=s3+num[v];
end;
end;
第三节 动态规划解题及应用技巧
一、 多进程Dp
这类题目的典型例子就是三取方格数。由于我们发现,在以原点为一侧的各组斜线上的每个点代表着第x次取到的所有点的集合。因此我们的状态转移就转化为各组斜线之前的关系。对于每组斜线的状态有2^k种,通过枚举当前状态以及寻找前驱状态来求解。
二、 滚动数组优化内存
这种优化方法多用于状态i只与状态i-1有关的一类问题。比如装箱问题、LCS等等。使用方法十分简单:
For i:=1 to n do
Dp[odd(i),f(x)]=dp[odd(i-1),g(x)]+h(i);
定义数组的时候可以
Var
Numset:array[boolean,1..maxn] of integer;
如此定义。因为boolean同样是一个子界,因此是正确的。
三、 DFS+DP
这类问题比较少见,主要是先dfs枚举可行方案或者可行解,再通过dp来求解可行性。可以通过做Vijos的“邮票问题”来加深印象。
附录
动态规划的术语
1.阶段
把所给求解问题的过程恰当地分成若干个相互联系的阶段,以便于求解,过程不同,阶段数就可能不同.描述阶段的变量称为阶段变量。在多数情况下,阶段变量是离散的,用k表示。此外,也有阶段变量是连续的情形。如果过程可以在任何时刻作出决策,且在任意两个不同的时刻之间允许有无穷多个决策时,阶段变量就是连续的。
在前面的例子中,第一个阶段就是点A,而第二个阶段就是点A到点B,第三个阶段是点B到点C,而第四个阶段是点C到点D。
2.状态
状态表示每个阶段开始面临的自然状况或客观条件,它不以人们的主观意志为转移,也称为不可控因素。在上面的例子中状态就是某阶段的出发位置,它既是该阶段某路的起点,同时又是前一阶段某支路的终点。
在前面的例子中,第一个阶段有一个状态即A,而第二个阶段有两个状态B1和B2,第三个阶段是三个状态C1,C2和C3,而第四个阶段又是一个状态D。
过程的状态通常可以用一个或一组数来描述,称为状态变量。一般,状态是离散的,但有时为了方便也将状态取成连续的。当然,在现实生活中,由于变量形式的限制,所有的状态都是离散的,但从分析的观点,有时将状态作为连续的处理将会有很大的好处。此外,状态可以有多个分量(多维情形),因而用向量来代表;而且在每个阶段的状态维数可以不同。
当过程按所有可能不同的方式发展时,过程各段的状态变量将在某一确定的范围内取值。状态变量取值的集合称为状态集合。
3.无后效性
我们要求状态具有下面的性质:如果给定某一阶段的状态,则在这一阶段以后过程的发展不受这阶段以前各段状态的影响,所有各阶段都确定时,整个过程也就确定了。换句话说,过程的每一次实现可以用一个状态序列表示,在前面的例子中每阶段的状态是该线路的始点,确定了这些点的序列,整个线路也就完全确定。从某一阶段以后的线路开始,当这段的始点给定时,不受以前线路(所通过的点)的影响。状态的这个性质意味着过程的历史只能通过当前的状态去影响它的未来的发展,这个性质称为无后效性。
4.决策
一个阶段的状态给定以后,从该状态演变到下一阶段某个状态的一种选择(行动)称为决策。在最优控制中,也称为控制。在许多问题中,决策可以自然而然地表示为一个数或一组数。不同的决策对应着不同的数值。描述决策的变量称决策变量,因状态满足无后效性,故在每个阶段选择决策时只需考虑当前的状态而无须考虑过程的历史。
决策变量的范围称为允许决策集合。
5.策略
由每个阶段的决策组成的序列称为策略。对于每一个实际的多阶段决策过程,可供选取的策略有一定的范围限制,这个范围称为允许策略集合。允许策略集合中达到最优效果的策略称为最优策略。
给定k阶段状态变量x(k)的值后,如果这一阶段的决策变量一经确定,第k+1阶段的状态变量x(k+1)也就完全确定,即x(k+1)的值随x(k)和第k阶段的决策u(k)的值变化而变化,那么可以把这一关系看成(x(k),u(k))与x(k+1)确定的对应关系,用x(k+1)=Tk(x(k),u(k))表示。这是从k阶段到k+1阶段的状态转移规律,称为状态转移方程。
6.最优性原理
作为整个过程的最优策略,它满足:相对前面决策所形成的状态而言,余下的子策略必然构成“最优子策略”。
最优性原理实际上是要求问题的最优策略的子策略也是最优。让我们通过对前面的例子再分析来具体说明这一点:从A到D,我们知道,最短路径是AàB1àC2àD,这些点的选择构成了这个例子的最优策略,根据最优性原理,这个策略的每个子策略应是最优:AàB1àC2是A到C2的最短路径,B1àC2àD也是B1到D的最短路径……事实正是如此,因此我们认为这个例子满足最优性原理的要求。