使用动态规划技术的必要条件----满足最优子结构性质和重叠子问题性质:
- 最优子结构性质:问题的最优解由相关子问题的最优解组合而成.[1]
- 重叠子问题性质:如果递归算法反复求解相同的子问题,就称该问题具有重叠子问题性质.
动态规划一般可分为 背包动规,线性动规,区间动规,树形动规
背包动规
(可以参考<背包九讲>)
01背包(完全背包,多重背包,二维背包)
01背包问题一般有两个特征----"组合"和"资源",并且组合的选择对资源有影响.在最原始的01背包问题中,"组合"就是选取了哪些物品,"资源"就是背包的容量.每次决策就是对第i件物品选择"放或不放",如果选择放就会导致背包剩余容量减少(对资源产生影响).当然在由01背包延伸出的问题中,第i件"物品"不一定只有两种选择(poj1837-Balance).理论上完全背包,多重背包以及二维背包问题都能转化为01背包问题.
例子:
poj3624-Charm Bracelet:
[问题描述]:给定n个物品,每个物品i有重量w[i]和价值d[i],背包容量为M,要求在不超过背包容量的前提下,使背包中物品的价值最大(每个物品只能出现一次)
[解题思路]:f[n,v]表示只考虑前n个物品且恰好装满空间为v的背包所得到的最大价值,则f[n,v]=max{f[n-1,v],f[n-1][v-w[n]]+d[n]}
poj1837-Balance
[问题描述]:n和m分别表示钩子数和砝码数,p[i]表示第i个钩子到支点的距离(负值代表钩子在左端),w[i]表示第i个砝码的重量,要求所有砝码都要用,求有多少种方案可以使得天平左右平衡
[解题思路]:问题规模是砝码个数和"平衡值",定义状态(n,value)表示用前n个砝码,得到平衡值为value的方法数,其中value=sum(w[i]*p[ki]),1<=i<=n,ki表示第i个砝码放的位置,得到递推式f(n,value)+=f(n-1,value-w[n]*p[kn]).因为value的最小值为-7500,所以定义value'=value+7500,得到新递推式:f(n, value')+=f(n-1,value'-w[n]*p[kn])
poj3628-Bookshelf2
[问题描述]:有n头奶牛,每头奶牛i有身高h[i],有一个高为B的书架,要求选出奶牛的一个子集J,使得sum{h[i]}-B最小且sum{h[i]}-B>=0(i属于J)
[解题思路]:考虑令f[n]表示前n头奶牛所能达到的"最好高度”(指超出B且最小的高度和),但是这并不能满足最优子结构,因为原问题的解和子问题的解没有依赖关系.令f[n][H]表示前n头牛中是否存在一个集合使得其高度和为H,则f[n][H]=f[n-1][H] || f[n-1][H-h[n]]
poj1745-Divisibility
[问题描述]:有n个数{v1,v2,...,vn},以及一个数K,在这N个数之间添加+或-得到一个表达式,判断该表达式的值能否被K整除
[解题思路]:令f[n][x]表示前n个数能否通过添加+,-得到一个表达式,使得该表达式的值除以K的余数为x.则f[n][x]=f[n-1][(x-v[n]+K*10000)%K]||f[n-1][(x+v[n]+K*10000)%K]
poj1976-A Mini Locomotive
[问题描述]:有n节车厢,每节车厢有v[i]个乘客,有3列火车,每列火车只能拉连续的M节车厢,求最多能拉多少乘客.
[解题思路]:f[i][j]表示用j列火车去拉前i节厢的乘客,最多能拉多少人,则f[i][j]=max{f[i-1][j], f[i-M][j-1]+sum{v[i-M+1],...,v[i]} },用s[i]表示前i节车厢的乘客总数,则f[i][j]=max{f[i-1][j], f[i-M][j-1]+s[i]-s[i-M]}
poj2576-Tug of War
[问题描述]:n个人,体重分别为w[1],w[2],...,w[n],将n个人分为两组,人数只差不得大于1.要求两组的体重和最接近.
[解题思路]:f[n][i][j]表示考虑前n个人时,第一队能否达到体重和为i且人数为j这一状态,则f[n][i][j]=f[n-1][i][j] || f[n-1][i-w[n]][j-1]
线性动规
这类问题一般在线性结构上进行状态转移,典型的如最大子段和,最长公共子序列(LCS),最长上升子序列(LIS)等.
最大子段和问题
poj2593-Max Sequence
[问题描述]:找出两个子段使得它们的和最大
[解题思路]:f[i]表示以第i个数结尾的最大子段和,f[i]=max(v[i],v[i]+f[i-1]);g[i]表示以第i个数开始的最大子段和,g[i]=max(v[i],v[i]+g[i+1]).h[i]=max{f[i],f[i-1],...,f[1]}=max{f[i],h[i-1]}
poj1050-To the Max
[问题描述]:用矩阵内部的所有元素和来衡量一个矩阵的大小,求最大子矩阵的元素和
[解题思路]:令s[i][j][row]表示sum(A[i][row],A[i+1][row],...A[j][row]),s[i][j][row]=sum(A[1][row],...,A[j][row])-sum(A[1][row],...,A[i][row]),f[i][j][k]表示数组s[i][j]的以第k个元素结尾的最大子段和,则f[i][j][k]=max(s[i][j][k],s[i][j][k]+f[k-1])
最长公共子序列
poj1458-Common Subsequence
[问题描述]:求两个序列的最长公共子序列的长度
[解题思路]:f[i][j]表示序列str1[1,...i]与序列str2[1,...j]的最长公共子序列的长度,则f[i][j]的值可以通过比较两个序列的最后一个元素得到:f[i][j]=(str1[i]==str2[j]?f[i-1][j-1]+1:max(f[i][j-1],f[i-1][j]))
poj1159-Palindrome
[问题描述]:已知一个序列,求至少添加多少个元素使得该序列成为一个回文序列
[解题思路]:需要添加的元素数=原序列长度-原序列与逆序列的最长公共子序列长度,从而将问题转换为最长公共子序列问题.由于每次f[i][j]的决策只需要考虑f[i-1][j-1],f[i-1][j],f[i][j-1],可以采用滚动数组对空间进行优化.
最长上升子序列
poj2533-Longest Ordered Subsequence
[问题描述]:求最长上升子序列的长度
[解题思路]:f[i]表示以i结尾的最长上升子序列的长度,则f[i]=max{f[j] | v[j] < v[i] && j < i}
区间动规
这类问题的特征在于能将原问题分解为两两合并的形式,通过枚举合并点,将问题分解为左右两个部分,最后将两个部分的最优值进行合并得到原问题的最优值.典型的问题有:矩阵链乘,石子合并,能量项链等
poj1160-Post Office
[问题描述]:有n个村庄,其位置分别为x[1],x[2],...,x[n],从这n个村庄中选出m个设立邮局,要求每个村庄到它最近邮局的距离之和最小
[解题思路]:f[i][j]表示在前j个村庄设i个邮局所得到的最小距离和,则f[i][j]=min(f[i-1][k]+w[k+1][j]),实际上这是枚举了最后一个邮局所能支配的村庄范围.w[k+1][j]表示在第k+1到第j个村庄设一个邮局的最小距离和,显然应该将唯一的邮局设在中间那个村庄.令p[i][j]表示使得f[i][j]最小的那个k,根据四边形不等式优化可知p[i-1][j]<=p[i][j]<=p[i][j+1],从而大大减少k的枚举范围.
四边形不等式优化:
证明过程可以参考毛子青的论文<动态规划算法的优化技巧>
一般对于诸如\(m(i,j)=min \ {(i,k)+m(k,j)}+w(i,j)\}\)等递推方程,先通过\(w(i,j)\)满足四边形不等式推出\(m(i,j)\)满足四边形不等式,即对于\(i \lt i' \lt j \lt j'\)有\(m(i,j)+m(i',j') \le m(i',j)+m(i,j')\),这步是证明的难点;定义\(s(i,j)\)为\(m(i,j)\)对应的决策变量的最大值,则可通过\(w(i,j)\)满足四边形不等式推出\(s(i-1,j) \le s(i,j) \le s(i,j+1)\)或\(s(i,j-1) \le s(i,j) \le s(i+1,j)\),利用这点可以大大减少\(k\)的枚举范围.
树形动规
这类问题一般可以把状态表示成一棵树,某棵子树根结点的目标函数值依赖于它的子结点(或是子结点的目标函数值依赖于根节点),可以采用自底向上进行动态规划(或是自顶向下).
poj2342-Anniversary party
[问题描述]:大学聚会,要求到场的任何两个人都不是上司/下属关系,每个人都有一个高兴值c[i],可正可负,要求让总的高兴值最大
[解题思路]:f[i][0]表示以i为根的子树的最大高兴值且第i个人不去,f[i][1]表示以i为根的子树的最大高兴值且第i个人去,则f[i][0]=sum(max(f[k][0],f[k][1])),f[i][1]=c[i]+sum(f[k][0]),其中k是i的下属