1.1k likes | 1.3k Views
Dynamic Programming. 目录. DP 适用问题第一特征:重叠子问题 Fibonacci Number Binomial Coefficients Shortest paths in DAGs DP 适用问题第二特征:最优子结构 Chain matrix multiplication Longest Increasing Subsequences 运用 DP 解决问题的步骤. 目录. DP 经典问题 Knapsack 背包问题 硬币问题 树状 DP Edit Distance The Partition Problem. 简介.
E N D
目录 • DP适用问题第一特征:重叠子问题 • Fibonacci Number • Binomial Coefficients • Shortest paths in DAGs • DP适用问题第二特征:最优子结构 • Chain matrix multiplication • Longest Increasing Subsequences • 运用DP解决问题的步骤
目录 • DP经典问题 • Knapsack 背包问题 • 硬币问题 • 树状DP • Edit Distance • The Partition Problem
简介 • 动态规划(Dynamic Programming, DP)是在20世纪50年代由一位卓越的美国数学家Richcard Bellman发明的。它作为一种重要的工具在应用数学中被广泛的应用。它不仅可以解决特定类型的优化问题,还可以作为一种通用的算法设计技术为计算机服务。
DP适用问题的第一特征 • 所求解的问题具有重叠子问题。 • 解决方法 • 进行记忆化求解。(用空间换时间) • 依照某种合适次序计算,以避免重复计算。
Fibonacci Number • 求Fibonacci数: f(n) = f(n-1) + f(n-2) n>1 f(0)=0, f(1)=1
Fibonacci Number • 常规递归 long long fib_r(int n) { if (n == 0) return 0; if (n == 1) return 1; return (fib_r(n-1) + fib_r(n-2)); } • 指数级时间复杂度,无法忍受。
Fibonacci Number • 常规递归 • 重叠子问题,导致重复计算。
Fibonacci Number • 自顶而下,记忆化求解 long longfib_c(int n) { if (f[n] == -1) f[n] = fib_c(n-1) + fib_c(n-2); return (f[n]); } long longfib_c_driver(int n) { f[0] = 0; f[1] = 1; for (inti=2; i<=n; i++) f[i] = -1; return (fib_c(n)); }
Fibonacci Number • 自底而上,顺序求解 long longfib_dp(int n) { long long f[MAXN + 1]; f[0] = 0; f[1] = 1; for (inti=2; i<=n; i++) f[i] = f[i-1] + f[i-2]; return (f[n]); }
Fibonacci Number • 自底而上,顺序求解。空间优化 long longfib_ultimate(int n) { /* back2,back1 are last two values of f[n] */ long long back2 = 0, back1 = 1; long long next; if (n==0) return 0; for (inti=2; i<n; i++) { next = back1 + back2; back2 = back1; back1 = next; } return (back1+back2); }
Binomial Coefficients • C(n,k)表示从n个不同物体中选出k个的组合数 • C(n,k)=n!/((n−k)!k!)=C(n-1,k-1)+C(n-1,k) Pascal’s triangle Matrix representation
Binomial Coefficients Const int MAXN = 100; long long binomial_coefficient(int n, int k) { long long bc[MAXN+1][MAXN+1]; for (int i = 0; i<=n; i++) bc[i][0] = 1; for (int j = 0; j<=n; j++) bc[j][j] = 1; for (int i = 1; i<=n; i++) for (int j=1; j<i; j++) bc[i][j] = bc[i-1][j-1] + bc[i-1][j]; return ( bc[n][k] ); }
Shortest paths in DAGs 以S为源点对左图中的所有结点进行拓扑排序,得到右图。则计算从源点S到点D的最短路径可以表示为: dist(D) = min{dist(B)+edge(BD), dist(C)+edge(CD)} = min{dist(B)+1, dist(C)+3} 这里的B和C是结点D的两个前驱结点。
Shortest paths in DAGs Topologically sort vertices in G; Initialize all dist(.) values to INF dist(s) = 0 for each v ∈ V , in linearized order do dist(v) = min(u,v) ∈E { dist(u) + w(u,v) }
Example 求s到任意点的最短距离 6 1 u r t s v w 2 7 –2 5 –1 0 4 3 2
Example 6 1 u r t s v w 2 7 –2 5 –1 0 4 3 2 dist[r] = , dist[s] = 0
Example 6 1 u r t s v w 2 7 –2 5 –1 0 2 6 4 3 2
Example 6 1 u r t s v w 2 7 –2 5 –1 0 2 6 6 4 4 3 2 dist[t]=min{dist[s]+2, dist[r]+3}
Example 6 1 u r t s v w 2 7 –2 5 –1 0 2 6 5 4 4 3 2 dist[u]=min{dist[s]+6, dist[t]+7}
Example 6 1 u r t s v w 2 7 –2 5 –1 0 2 6 5 3 4 3 2 dist[v]=min{dist[t]+4, dist[u]-1}
Example 6 1 u r t s v w 2 7 –2 5 –1 0 2 6 5 3 4 3 2 dist[w]=min{dist[t]+2, dist[u]+1, dist[v]-2}
动态规划术语 • 状态:贴切,简洁的描述出事物性质的单元量。例如:Dist[x]。 要求:状态与状态之间可以转移,以便有初始状态逐渐转移到目标状态,找到问题的解。 • 阶段:若干性质相近可以同时处理的状态的集合。就是计算状态的顺序。 要求:每个阶段中状态的取值只与这个阶段之前的阶段中的状态有关,与这个阶段之后的阶段中的状态无关。
动态规划术语 • 状态转移方程:前一个阶段中的状态转移到后一个阶段的状态得演变规律,即相邻两个阶段的状态变化方程。 Dk(i) = opt { Dk-1(j) + cost(i,j) } 第k阶段的i状态与第k-1阶段的j状态有关 • 决策:计算每个状态时作出的选择。
动态规划时间效率 动态规划解决问题的方法是通过解决很多小的问题而解决大问题。 动态规划的效率取决于两个因素:子问题的数量和子问题的解决效率。 时间效率:子问题的数量*子问题的时间效率。
DP适用问题的第二特征 • 所求解的问题具有最优子结构,即问题的一个最优解中包含子问题的最优解。 • 例如,矩阵链乘法问题中,Ai×Ai+1×…×Aj的一个最优加全部括号把乘积在和之间分裂,它包含了两个子问题Ai×Ai+1×…×Ak和Ak+1×Ak+2×…×Ak的最优解。
Chain matrix multiplication For 1≤ i≤k≤j ≤n, define C(i,j) = minimum cost of multiplying Ai×Ai+1×…×Aj for i = 1 to n: C(i, i) = 0 for s = 1 to n - 1: for i = 1 to n - s: j = i + s C(i, j) = min{C(i, k) + C(k + 1, j) +mi-1·mk·mj : i≤k ≤j} return C(1, n) The subproblems constitute a two-dimensional table, each of whose entries takes O(n) time to compute. The overall running time is thus O(n3).
Chain matrix multiplication • 矩阵链相乘问题具有最优子结构,即问题的一个最优解中包含子问题的最优解。 • Ai×Ai+1×…×Aj的一个最优加全部括号把乘积在和之间分裂,它包含了两个子问题Ai×Ai+1×…×Ak和Ak+1×Ak+2×…×Ak的最优解。
相关习题 sicily1345 能量项链 • 给出一串项链,每次可以选相邻两个珠子进行聚合,释放出一定的能量,并产生一个新珠子。项链是头尾相接的。求释放的能量的总和的最大值。 • 项链长度不超过100。
Longest increasing subsequence • Algorithm:
Longest Increasing Subsequences L(0) = 1, P(.) = -1 For i = 1, 2, …, n L(i) = 1 + max0<j<i{L(j)}, where Sj<Si P(i) = j L(i) is the length of the longest path ending at i (plus 1). Time complexity is O(n2), the maximum being when the input array is sorted in increasing order.
相关习题 • 推荐题目: sicily1060 Bridging Signals (需要用特殊法做) sicily1685 Missile(最长不单调子序列,数据小,O(n2)) sicily1448 Antimonotonicity(最长不单调子序列,数据大,O(n))
小结 • 动态规划的一般步骤 • 考察问题结构,给出状态表示方式,列出递归方程(状态转移方程) • 分析复杂度,若复杂度较高则需要优化。 • 用递推或记忆化搜索实现。 • 根据计算最优值过程中的信息,递归的构造一个最优解。 • 应用动态规划的前提条件 • 重复子问题 • 最优子结构
背包问题 • 01背包问题 • 完全背包问题 • 多重背包问题 • 分组的背包问题 • 有依赖的背包问题
01背包问题 • 有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。 • 这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。 • 状态:用f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值,其状态转移方程为f[i][v]=max{ f[i-1][v], f[i-1][v-c[i]]+w[i] }。 • 复杂度分析: • 状态数:O(NV), 迁移:O(1) • 总复杂度:O(NV)
状态f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。状态f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。 • 对于“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。 • 如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v]; • 如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。 • 状态转移方程: f[i][v]=max{ f[i-1][v], f[i-1][v-c[i]]+w[i] }
时间复杂度O(NV) • 由于计算f[i][v]时只用到f[i-1][v]和f[i-1][v-c[i]],在每次主循环中我们以v=V..0的顺序推f[v],这样就保证推f[v]时f[v-c[i]]保存的是状态f[i-1][v-c[i]]的值,f[v]保存的是状态f[i-1][v]的值。 • 伪代码如下: for i=1..N for v=V..0 f[v]=max{f[v], f[v-c[i]] + w[i]}; • 空间复杂度成功降到O(V)
抽象成过程ZeroOnePack,表示处理一件01背包中的物品,两个参数cost、weight分别表明这件物品的费用和价值。抽象成过程ZeroOnePack,表示处理一件01背包中的物品,两个参数cost、weight分别表明这件物品的费用和价值。 void ZeroOnePack(cost,weight){ for v=V..cost f[v]=max{f[v],f[v-cost]+weight} } • 01背包问题的伪代码就可以这样写: for i=1..N ZeroOnePack(c[i],w[i]);
初始化问题 • 如果题目要求“恰好装满背包”时的最优解 在初始化时除了f[0]为0其它f[1..V]均设为-∞ • 如果并没有要求必须把背包装满,初始化时应该将f[0..V]全部设为0 • 初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。
推荐题目: sicily1782 Knapsack sicily1146 采药 sicily1342 开心的金明
完全背包问题 • 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。 • 状态f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值,其状态转移方程为 f[i][v]=max{ f[i-1][v], f[i][v-c[i]]+w[i] } • 时间复杂度O(NV)
完全背包问题 • O(VN)的算法 空间优化,伪代码: for i=1..N for v=0..V f[v]=max{f[v],f[v-c[i]]+w[i]} void CompletePack(intcost,int weight) { for(int v=cost;v<=V;++v) f[v] >?= f[v-cost]+weight; }
01背包按照v=V..0的逆序来循环,要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。01背包按照v=V..0的逆序来循环,要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。 • 完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。即f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]}
二维背包 • 对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量). 问怎样选择物品可以得到最大的价值. 设第i件物品所需的两种代价分别为a[i]和b[i]. 两种代价可付出的最大值(两种背包容量)分别为V和U. 物品的价值w[i]. • 分析:费用加了一维,只需状态也加一维即可. 设f[i][v][u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值。状态转移方程就是: f[i][v][u]=max{ f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i] }
二维背包 • 二维背包很容易扩展到多维背包 • “二维费用”的条件有时是以这样一种隐含的方式给出的:最多只能取M件物品. 这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为1,可以付出的最大件数费用为M.