难点:模型的建立和理解。
要求:对于每道题,都能在很短的时间里把思路完整地整理一遍,可以快速而无误的写出程序。
(一) 两种动机
a) 利用重叠子问题,进行记忆化求解,即利用递归。
例1、 括号序列
分析:d[I,j]:=min
也可以用记忆化搜索。
需要的只有方程和边界。
例2、 棋盘分割[noi99]
目标式变形:
因为不变,所以要使每个矩阵的平方和尽量小。
F[k,x1,y1,x2,y2]为切k刀成为(x1,y1)-(x2,y2)的矩形的平方和的最小值。
F[k,x1,y1,x2,y2]:=
例3、 决斗[poi]
转换模型:若一人可以赢得比赛,只有自己能和自己做战时。
设meet[i,j]记录i,j是否能相遇。当i,j相遇时,只有i能和k相遇,k能和j相遇,并且k可以被i或j打败。
Meet[I,j]=
b) 把问题看作是多阶段决策过程。
例4、 舞蹈家怀特先生
对于一个状态,为了让它没有后效性,则必须记两个值,右脚的位置 and 左脚的位置。
无后效性是应用动态规划的重要条件。如果不满足无效后性,那么状态表示是不合理的,因为同一个状态可能对应很多本质不同的实例。
所以f[i]的状态不合理,f[I]àf[I,j]àf[I,j,k]
c) 总结及基本概念
J 无后效性和子结构: 本质相同,利用无后效性定义状态,利用最优子结构进行转移。状态和状态转移是动规的精髓。
J 状态表示和状态转移: 核心。消除后效性可以通过增加维数来实施,也可以在保持无后效性的情况下改变状态定义。但是有可能使子问题不重复,达不到效果。
J 两种实现方式:递推和记忆化搜索。递归很灵活,记忆化搜索教好写,并可以避免无用的状态,但是很难用到滚动数组来优化。
1 经典的方程f[i]:=max(f[j]+1) a[i]< or > a[j]
a) 直接做o(n2)
b) 加上树状数组,有时需要离散化,较容易理解,可以直接更新,应用范围有局限性。复杂度o(nlogn)
c) 二分优化
这种方法不好理解,但是适用性较强。
主体
Procedure; Begin L:=0;r:=ee; While l<r do Begin Mid:=(l+r+1) shr 1; If yes(需要量情况而定,并且要注意是找第一大于等于,或是大于,或是小于,或是小于等于的) then r:=mid-1 else l:=mid; End; If l=ee then inc(ee); Inc(l);b[l]:=a[i]; End; |
最后的ee就是答案,而事实上如果要记下每一长度的所有的能够得到的值也十分方便,还可以略掉中间值相同的。
例题:求一个序列的最长下降序列,并输出不同的方案。
分析:首先对于前面的值若和后面的一样,那么一定取后面的更优,所以最主要的是去掉相等的值。
主体
在上一段后加上 Procedure Begin Next[i]:=top[l];top[l]:=I; While a[next[i]]=a[i] do next[i]:=next[next[i]]; J:=top[l-1]; While a[j]<=a[i] do Begin G[i]:=g[i]+g[j];j:=next[j]; End; If g[i]=0 then g[i]:=0; End; |
这里还需要a[n+1]=-maxlongint,这样方案数就直接是g[n+1].
2 分段的动规,有的时候,动规的两段之间没有什么关系,这时就可以分段地动规。
例 矩阵取数问题(noip2007)
对于一个n*m的矩阵,取m次,每次只能取行首和行末的,每次的分值就是每格的分值乘上2i的和。
分析 因为行的取的状态互不干扰,所以每一行一行的讨论。
这里用的是区间动规,并不是很明显,需要分析。区间动规一般不太明显的。
3 双线程动规
例 传纸条
从矩阵的(1,1)开始,走两条不重合的路径,使得总和最大。
分析 因为有两条路径,所以使其同时出发,建一个四维的方程。
F[I1,j1,i2,j2],状态转移从上和左转移过来。但是f[I,j,I,j]=-maxlongint。
4 一些模型
a) 线性动规
i. F[i]={f[j]+?} i<j
例 尼克的任务
尼克需要完成一些任务,一开始就不能停止,问获得的最大空闲时间。
分析 显然这样推下来,但是由于一旦开始就不能停止,所以需要注意的是要从后往前推,因为若处于开始时间,就不能从i+1推到前面来,只能从en[i]推出来。
ii. 有二维的f[I,j]=max{f[i-1,jj]+?}
这种方程便可以滚动优化空间,例如最经典的背包,这种方程用得较多。如书的复制、多米诺骨牌。
iii. 这种线性动规经常会有后效性,这时就需要加维来剪掉后效性。
b) 串动规
i. 会用到串匹配的性质,会有点难,如加上kmp之类的优化。
c) 判定性动规
i. 这一类的动规只需要写上是否存在,一般难以想到,有时可以将答案比较小的动规变成判定性动规来写。
d) 区间动规
i. F[I,j]={f[ii,jj]+?}
有时会在串动规中用到。
e) 状态压缩动规
i. 有点难度,但是一看数据范围容易想到。转移一般是用记忆化搜索。
例 平板涂色
一种板,分成了不同的区域,涂一种颜色之前,需要将其上面的涂完,问最多拿多少次刷子。
分析 因为矩形个数<=16,所以很快想到状态压缩动态规划。
例 单词游戏
单词首尾相连,使得长度最长。
分析 再次发现一个很好的东西,单词数<=16就不用说了吧!!
ii. 但是这里必须要注意判断状态的合理性。
f) 树型动规
i. 一些根本不像树规的树规
例 电报公司
给一串字符串中的字母给三进制的数,使其不重复,求最小长度。
分析 因为最多就只有m层,所以在底下时实际上是一棵树。
F[d,I,j]=min{f[d-1,I,jj]+f[d-1,jj+1,kk]+f[d-1,kk+1,j]+s[j]-s[i-1]}(每一层长度+1)
ii. 一些特像树规的树规
有时候可以当作贪心来做。
iii. 和其它动规混在一起。
iv. 树规的实现
1. 边权的问题
例 在一棵树里,需要选一些节点,但是它连向根节点的边都要选,求出在这棵树中取k个根节点,使得点权-边权最大。
分析 方程十分简单
F[I,j]表示i中取j个的最大值,
F[I,j]=max(f[i1,j1]+f[i2,j2]+f[i3,j3]+…+f[In,jn]-va);
但是,转移起来可能有点儿麻烦,一般用搜索做,当然,拓扑也是可以的。
因为每一层相当于背包,最大值实际上就是f[I,j]=g[n,j]表示前n个背包中重量为j时的最大值。G[n,j]=max{g[n-1,j],g[n-1,j-k]+f[in,k]-va[in]}。
所以直接按顺序做,加一个滚动数组(实际上是downto)优化空间。G[n,0]肯定为0。
2. 点值的问题
例 没有上司的晚会,选一些点,并且它选了后儿子就不能选了,求最大值。
分析 这个可以贪心直接做,背包反而麻烦了。因为边上的权值没有,所以可以转为二叉树。只是要注意右儿子状态。
3. 有依赖的背包问题
例 选课
一些课有先修课,选择m门课,使得在满足条件的基础上,点值总和最大。
分析 和上一题差不多。