文章目录
一、前言
目前本专栏正在进行优惠活动,在博主主页添加博主好友,可以获取 付费专栏优惠券。
不知道现在的你是否和我有同样的想法,就是对于一件事情,一直在犹豫到底要不要去做,如果你也有这样一件事情让你一直纠结着,那么我劝你,不要犹豫,立刻果断坚决的执行!因为最后的结果一定只有两个:做或是不做。任何的犹豫都是在浪费你的青春!
就像我决定重新梳理 《夜深人静写算法》 系列一样,之前纠结了好久要不要重写,因为看到自己以前写的文章,就好像看到刚接触代码的自己,作为程序员,看到自己以前写的代码,只有一个想法,那就是重构!
幸好这件事情没有让我纠结太久,一旦开始就意味着成功了一半,奔跑吧,少年,让天下没有难学的算法!
二、动态规划初探
1、递推
- 暂且先不说动态规划是怎么样一个算法,由最简单的递推问题说起应该是最恰当不过得了。
- 一来,递推的思想非常浅显,从初中开始就已经有涉及,等差数列 f [ i ] = f [ i − 1 ] + d f[i] = f[i-1] + d f[i]=f[i−1]+d( i > 0 i > 0 i>0, d d d 为公差, f [ 0 ] f[0] f[0] 为初项)就是最简单的递推公式之一;
- 二来,递推作为动态规划的基本方法,对理解动态规划起着至关重要的作用。理论的开始总是枯燥的,所以让读者提前进入思考是最能引起读者兴趣的利器,于是【例题1】应运而生。
【例题1】在一个 3 × n 3 \times n 3×n 的长方形方格中,铺满 1 × 2 1 \times 2 1×2 的骨牌(骨牌个数不限制),给定 n n n,求方案数(图二 -1-1为 n = 2 n = 2 n=2 的所有方案),所以 n = 2 n = 2 n=2 时方案数为 3 3 3。
图二 -1-1
- 这是一个经典的递推问题,如果觉得无从下手,我们可以来看一个更加简单的问题,把问题中的 “3” 变成 “2”(即在一个
2
×
n
2 \times n
2×n 的长方形方格中铺满
1
×
2
1 \times 2
1×2 的骨牌的方案)。这样问题就简单很多了,我们用
f
[
i
]
f[i]
f[i] 表示
2
×
i
2 \times i
2×i 的方格铺满骨牌的方案数,那么考虑第
i
i
i 列,要么竖着放置一个骨牌;要么连同
i
−
1
i-1
i−1 列,横着放置两个骨牌。
图二 -1-2 - 如图二-1-2所示。由于骨牌的长度为 1 × 2 1 \times 2 1×2,所以在第 i i i 列放置的骨牌无法影响到第 i − 2 i-2 i−2 列。很显然,图二 -1-2 中两块黑色的部分分别表示 f [ i − 1 ] f[i-1] f[i−1] 和 f [ i − 2 ] f[i-2] f[i−2],所以可以得到递推式:
f[0] = f[1] = 1;
for(int i = 2; i <= n; ++i) {
f[i] = f[i-1] + f[i-2];
}
- 再回头来看 3 × n 3 \times n 3×n 的情况,首先可以明确当 n n n 等于奇数的时候,方案数一定为 0 0 0。
- 所以如果用
f
[
i
]
f[i]
f[i] (
i
i
i 为偶数) 表示
3
×
i
3 \times i
3×i 的方格铺满骨牌的方案数,
f
[
i
]
f[i]
f[i] 的方案数不可能由
f
[
i
−
1
]
f[i-1]
f[i−1] 递推而来。那么我们猜想
f
[
i
]
f[i]
f[i] 和
f
[
i
−
2
]
f[i-2]
f[i−2] 一定是有关系的,如图二 -1-3所示,我们把第
i
i
i 列和第
i
−
1
i-1
i−1 列用
1
×
2
1 \times 2
1×2 的骨牌填满后,轻易转化成了
f
[
i
−
2
]
f[i-2]
f[i−2] 的问题,那是不是代表
f
[
i
]
=
3
∗
f
[
i
−
2
]
f[i] = 3 * f[i-2]
f[i]=3∗f[i−2] 呢?
图二 -1-3 - 仔细想想才发现不对,原因是我们少考虑了图二 -1-4的情况,这些情况用图一 -1-3的情况无法表示,再填充完黑色区域后,发现和
f
[
i
−
4
]
f[i-4]
f[i−4] 也有关系,但是还是漏掉了一些情况。
图二 -1-4 - 上面的问题说明我们在设计状态时候的思维定式,当一维的状态已经无法满足我们的需求时,我们可以试着增加一维,用二维来表示状态,用
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示
(
3
×
i
)
+
j
(3 \times i) + j
(3×i)+j 个多余块的摆放方案数,如图二 -1-5所示:
图二 -1-5 - 转化成二维后,我们可以轻易写出三种情况的递推式,具体推导方法见图二 -1-6。
f[0][0] = f[1][1] = f[0][2] = 1;
for(int i = 2; i <= n; ++i) {
f[i][0] = f[i-2][0] + f[i-1][1] + f[i-2][2];
f[i][1] = f[i-1][2];
f[i][2] = f[i][0] + f[i-1][1];
}
- 如果 n n n 不是很大的情况,到这一步,我们的问题已经完美解决了,其实并不需要求它的通项公式,因为我们是程序猿,一个 for 循环就能搞定了,接下来的求解就全仰仗于计算机来完成了。
【例题2】对一个 “01” 串进行一次 μ 变换被定义为:将其中的 “0” 变成 “10”,“1” 变成 “01”,初始串为 “1”,求经过 n ( n ≤ 1000 ) n(n \le 1000) n(n≤1000) 次 μ 变换后的串中有多少对 “00”(有没有人会纠结会不会出现 “000” 的情况?这个请放心,由于问题的特殊性,不会出现 “000” 的情况)。图二 -1-7表示经过小于4次变换时串的情况。
图二 -1-7
- 如果纯模拟的话,每次 μ 变换串的长度都会加倍,所以时间和空间复杂度都是 O ( 2 n ) O(2^n) O(2n),对于 n = 1000 n = 1000 n=1000 的情况,完全不可能计算出来。仔细观察这个树形结构,可以发现要出现 00,一定是 10 和 01 相邻产生的。为了将问题简化,我们不妨设: A = 10 , B = 01 A = 10, B = 01 A=10,B=01
- 构造出的树形递推图如图二 -1-8所示,如果要出现 00,一定是 AB(1001)。
图二 -1-8 - 令 f [ i ] [ 0 ] f[i][0] f[i][0] 为 A 经过 i 次 μ 变换后 00 的数量,则 f [ 0 ] [ 0 ] = 0 f[0][0] = 0 f[0][0]=0, f [ i ] [ 1 ] f[i][1] f[i][1] 为 B 经过 i i i 次 μ 变换后 00 的数量, f [ 0 ] [ 1 ] = 0 f[0][1] = 0 f[0][1]=0。
- 从图中观察得出,以A为根的树,它的左子树的最右端点一定是B,也就是说无论经过多少次变换,两棵子树的交界处都不可能产生AB,所以有:
- f [ i ] [ 0 ] = f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] f[i][0] = f[i-1][0] + f[i-1][1] f[i][0]=f[i−1][0]+f[i−1][1]
- 而以 B 为根的树,它的左子树的右端点一定是A,而右子树的左端点呈BABABA…交替排布,所以隔代产生一次AB,于是 f [ i ] [ 1 ] = f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] + ( i m o d 2 ) f[i][1] = f[i-1][0] + f[i-1][1] + (i \ mod \ 2) f[i][1]=f[i−1][0]+f[i−1][1]+(i mod 2) 最后要求的答案就是 f [ n − 1 ] [ 1 ] f[n-1][1] f[n−1][1],递推求解。
f[0][0] = f[0][1] = 0;
for(int i = 1; i <= 1000; i++) {
f[i][0] = f[i-1][0] + f[i-1][1];
f[i][1] = f[i-1][0] + f[i-1][1] + (i % 2);
}
2、状态和状态转移
- 在介绍递推的时候,涉及到一个词—状态,它表示了解决某一问题的中间结果,这是一个比较抽象的概念,例如【例题1】中的 f [ i ] [ j ] f[i][j] f[i][j],【例题2】中的 f [ i ] [ 0 ] f[i][0] f[i][0]、 f [ i ] [ 1 ] f[i][1] f[i][1],求解问题的时候,首先要设计出合适的状态,然后通过状态的特征建立状态转移方程( f [ i ] = f [ i − 1 ] + f [ i − 2 ] f[i] = f[i-1] + f[i-2] f[i]=f[i−1]+f[i−2] 就是一个简单的状态转移方程)。
- 下文第四节会图解常用的状态转移方程。
3、最优化原理和最优子结构
- 如果问题的最优解包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。这里我尽力减少理论化的概念,而改用一个简单的例题来加深对这句话的理解。
【例题3】给定一个长度为 n ( 1 < = n < = 1000 ) n(1 <= n <= 1000) n(1<=n<=1000) 的整数序列 a [ i ] a[i] a[i],求它的一个子序列 (子序列即在原序列任意位置删除0或多个元素后的序列),满足如下条件:
1、该序列单调递增;
2、在所有满足条件 1 的序列中长度是最长的;
- 这个问题是经典的动态规划问题,被称为最长单调子序列。
- 我们假设现在没有任何动态规划的基础,那么看到这个问题首先想到的是什么?
- 我想到的是万金油算法—深度优先搜索( D F S DFS DFS ),即枚举 a [ i ] a[i] a[i] 这个元素取或不取,所有取的元素组成一个合法的子序列,枚举的时候需要满足单调递增这个限制,那么对于一个 n n n 个元素的序列,最坏时间复杂度自然就是 O ( 2 n ) O(2^n) O(2n) , n = 30 n = 30 n=30 就已经很变态了更别说是 1000 1000 1000。
- 然而,方向是对的,动态规划求解之前先试想一下搜索的正确性,这里搜索的正确性是很显然的,因为已经枚举了所有情况,总有一种情况是我们要求的解。我们尝试将搜索的算法进行一些改进,假设第 i i i 个数取的情况下已经搜索出的最大长度记录在数组中,即用 d [ i ] d[i] d[i] 表示当前搜索到的以 a [ i ] a[i] a[i] 结尾的最长单调子序列的长度,那么如果下次搜索得到的序列长度小于等于 d [ i ] d[i] d[i],就不必往下搜索了(因为即便继续往后枚举,能够得到的解必定不会比之前更长);反之,则需要更新 d [ i ] d[i] d[i] 的值。
- 如图二-3-1,红色路径表示第一次搜索得到的一个最长子序列1、2、3、5,蓝色路径表示第二次搜索,当枚举第3个元素取的情况时,发现以第3个数结尾的最长长度
d
[
3
]
=
3
d[3] = 3
d[3]=3,比本次枚举的长度要大(本次枚举的长度为2),所以放弃往下枚举,大大减少了搜索的状态空间。
图二-3-1 - 这时候,我们其实已经不经意间设计好了状态,就是上文中提到的那个
d
[
i
]
d[i]
d[i] 数组,它表示的是以
a
[
i
]
a[i]
a[i] 结尾的最长单调子序列的长度,那么对于任意的
i
i
i,
d
[
i
]
d[i]
d[i] 一定等于
d
[
j
]
+
1
(
j
<
i
)
d[j] + 1 \ ( j < i )
d[j]+1 (j<i),而且还得满足
a
[
j
]
<
a
[
i
]
a[j] < a[i]
a[j]<a[i]。因为这里的
d
[
i
]
d[i]
d[i] 表示的是最长长度,所以
d
[
i
]
d[i]
d[i] 的表达式可以更加明确,即:
d [ i ] = m a x ( d [ j ] ∣ j < i , a [ j ] < a [ i ] ) + 1 d[i] = max ( d[j] | j < i, a[j] < a[i] ) + 1 d[i]=max(d[j]∣j<i,a[j]<a[i])+1 - 这个表达式很好的阐释了最优化原理,其中 d [ j ] d[j] d[j] 作为 d [ i ] d[i] d[i] 的子问题, d [ i ] d[i] d[i] 最长(优)当且仅当 d [ j ] d[j] d[j] 最长(优)。当然,这个方程就是这个问题的状态转移方程。状态总数量 O ( n ) O(n) O(n), 每次转移需要用到前 i i i 项的结果,平摊下来也是 O ( n ) O(n) O(n) 的, 所以该问题的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。
4、决策和无后效性
- 一个状态演变到另一个状态,往往是通过“决策”来进行的。有了“决策”,就会有状态转移。而无后效性,就是一旦某个状态确定后,它之前的状态无法对它之后的状态产生“效应”(影响)。
【例题4】老王想在未来的 n n n 年内每年都持有电脑, m ( y , z ) m(y, z) m(y,z) 表示第 y y y 年到第 z z z 年的电脑维护费用,其中 y y y 的范围为 [ 1 , n ] [1, n] [1,n], z z z 的范围为 [ y , n ] [y, n] [y,n], c c c 表示买一台新的电脑的固定费用。 给定矩阵 m m m,固定费用 c c c,求在未来 n n n 年都有电脑的最少花费。
- 考虑第 i i i 年是否要换电脑,换和不换是不一样的决策,那么我们定义一个二元组 ( a , b ) (a, b) (a,b),其中 a < b a < b a<b,它表示了第 a 年和第 b 年都要换电脑(第 a 年和第 b 年之间不再换电脑),如果假设我们到第 a 年为止换电脑的最优方案已经确定,那么第 a 年以前如何换电脑的一些列步骤变得不再重要,因为它并不会影响第 b 年的情况,这就是无后效性。
- 接下来,会对这题进行一个详细的解释,当然看不懂没关系,可以跳过这个步骤,直接去看 第三章 - 动态规划的经典模型。毕竟,本文是入门级别的,后面还会花更多的时间来讲解动态规划的内容,可以和搜索一起逐步理解状态的概念。
- 更加具体得,令 d [ i ] d[i] d[i] 表示在第 i 年买了一台电脑的最小花费(由于这台电脑能用多久不确定,所以第 i 年的维护费用暂时不计在这里面),如果上一次更换电脑的时间在第 j 年,那么第 j 年更换电脑到第 i 年之前的总开销就是 c + m ( j , i − 1 ) c + m(j, i-1) c+m(j,i−1)
- 于是有状态转移方程:
d [ i ] = m i n ( d [ j ] + m ( j , i − 1 ) ∣ 1 < = j < i ) + c d[i] = min( d[j] + m(j, i-1) | 1 <= j < i ) + c d[i]=min(d[j]+m(j,i−1)∣1<=j<i)+c - 这里的 d [ i ] d[i] d[i] 并不是最后问题的解,因为它漏算了第 i 年到第 n 年的维护费用,所以最后问题的答案: a n s = m i n ( d [ i ] + m ( i , n ) ∣ 1 < = i < n ) ans = min( d[i] + m(i, n) | 1 <= i < n ) ans=min(d[i]+m(i,n)∣1<=i<n)
- 我们发现两个方程看起来很类似,其实是可以合并的,我们可以假设第 n+1 年必须换电脑,并且第 n+1 年换电脑的费用为 0,那么整个阶段的状态转移方程就是:
d
[
i
]
=
m
i
n
(
d
[
j
]
+
m
(
j
,
i
−
1
)
∣
1
<
=
j
<
i
)
+
w
(
i
)
d[i] = min( d[j] + m(j, i-1) | 1 <= j < i ) + w(i)
d[i]=min(d[j]+m(j,i−1)∣1<=j<i)+w(i)
w ( i ) = { c i < n + 1 0 i = n + 1 w(i) = \begin{cases} c & i < n+1\\ 0 & i=n+1 \end{cases} w(i)={c0i<n+1i=n+1 - d [ n + 1 ] d[n+1] d[n+1] 就是我们需要求的最小费用了。
三、动态规划的经典模型
- 本章节作者会通过图的方式,带读者了解一些基本模型,以加深对动态规划状态的理解;
- 黄色 ■ 代表当前状态;
- 绿色 ■ 代表子状态(已经求出的状态);
- 红色 ■ 代表尚未求出的状态;
- 灰色 ■ 代表永远不存在的状态;
1、线性模型
- 线性模型是动态规划中最常见的模型,上文讲到的最长单调子序列就是经典的线性模型。
- 线性模型的状态一般是通过 一维数组表示的,如图三-1-1所示,图中黄色块的状态为
d
[
i
]
d[i]
d[i],绿色块的状态为
d
[
j
]
d[j]
d[j],并且满足
(
j
<
i
)
(j < i)
(j<i),只有当
d
[
j
]
d[j]
d[j] 全部计算出来以后,
d
[
i
]
d[i]
d[i]的值才能够被确定。
图三-1-1 - 线性模型最经典的问题莫过于 背包问题 了,有关背包问题的内容,可以参考以下这篇文章:夜深人静写算法(十九)- 背包总览。
2、区间模型
- 对比线性模型,区间模型状态一般是通过:一个二维数组来表示的。
- 区间模型的状态表示一般为 d [ i ] [ j ] d[i][j] d[i][j],表示区间 [ i , j ] [i, j] [i,j] 上的最优解,最终要求的肯定是 [ 1 , n ] [1, n] [1,n] 的最优解。
- 如图三-2-1所示,既然是表示区间,所以对于状态
d
[
i
]
[
j
]
d[i][j]
d[i][j],当
i
>
j
i>j
i>j时,肯定是不合法的状态,所以标记为灰色;
d
[
i
]
[
j
]
d[i][j]
d[i][j] 表当前状态,标记为黄色;
图三-2-1 - 区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP。
3、树状模型
- 树形动态规划(树形DP),是指状态图是一棵树,状态转移也发生在树上,父结点的状态值通过所有子结点状态值计算完毕后得出,后续会专门开辟一个章节来讲述树形动态规划。
- 状态表示如图三-3-1所示。
图三-3-1
4、状态压缩模型
- 状态压缩的含义其实是对状态进行重新编码,来看下面这个例子。
- 假设状态是一个五维的数组,并且每一维的取值为
[
0
,
3
]
[0,3]
[0,3],状态表示如下:
d [ a ] [ b ] [ c ] [ d ] [ e ] ( 0 < = a , b , c , d , e < = 3 ) d[a][b][c][d][e] \\ (0 <= a,b,c,d,e <= 3) d[a][b][c][d][e](0<=a,b,c,d,e<=3) - 那么,写代码的过程中需要操作五维数组,十分繁琐,我们可以通过将状态压缩,将它重新编码到一个一维数组中。
- 其实只要能够找到一个映射函数,满足
x
x
x 和
(
a
,
b
,
c
,
d
,
e
)
(a,b,c,d,e)
(a,b,c,d,e) 一一映射,即:
f ( x ) = ( a , b , c , d , e ) f(x) = (a,b,c,d,e) f(x)=(a,b,c,d,e) - 因为每一维的取值为 [ 0 , 3 ] [0,3] [0,3],我们可以把每一维当成是 4进制数的每一位,于是有:
- x = a × 4 4 + b × 4 3 + c × 4 2 + d × 4 1 + e × 4 0 x = a \times 4^4 + b \times 4^3 + c \times 4^2 + d \times 4^1 + e \times 4^0 x=a×44+b×43+c×42+d×41+e×40
- 那么,我们只需要用一个一维数组来表示状态即可: d [ x ] d[x] d[x]。
四、动态规划的常用状态转移方程
动态规划算法三要素(摘自黑书,总结的很好,很有概括性):
①所有不同的子问题组成的表
②解决问题的依赖关系可以看成是一个图
③填充子问题的顺序(即对②的图进行拓扑排序,填充的过程称为状态转移);
- 则如果子问题的数目为 O ( n t ) O(n^t) O(nt),每个子问题需要用到 O ( n e ) O(n^e) O(ne) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n min min 或 m a x max max ), w ( j , i ) w(j, i) w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。
1、1D/1D
- d [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d[i] = opt( d[j] + w(j, i) | 0 <= i < j ) d[i]=opt(d[j]+w(j,i)∣0<=i<j)
- 状态转移如图四-1-1所示(黄色块代表
d
[
i
]
d[i]
d[i],绿色块代表
d
[
j
]
d[j]
d[j]):
图四-1-1 - 这类状态转移方程一般出现在线性模型中。
2、2D/0D
- d [ i ] [ j ] = o p t ( d [ i − 1 ] [ j ] + x i , d [ i ] [ j − 1 ] + y j , d [ i − 1 ] [ j − 1 ] + z i j ) d[i][j] = opt( d[i-1][j] + x_i, d[i][j-1] + y_j, d[i-1][j-1] + z_{ij} ) d[i][j]=opt(d[i−1][j]+xi,d[i][j−1]+yj,d[i−1][j−1]+zij)
- 状态转移如图四-2-1所示:
图四-2-1 - 比较经典的问题是最长公共子序列、最小编辑距离。
- 有关最长公共子序列的问题,可以参考以下文章:夜深人静写算法(二十一)- 最长公共子序列
- 有关最小编辑距离的问题,可以参考以下文章:夜深人静写算法(二十二)- 最小编辑距离
3、2D/1D
- d [ i ] [ j ] = w ( i , j ) + o p t ( d [ i ] [ k − 1 ] + d [ k ] [ j ] ) d[i][j] = w(i, j) + opt( d[i][k-1] + d[k][j] ) d[i][j]=w(i,j)+opt(d[i][k−1]+d[k][j])
- 区间模型常用方程,如图四-3-1所示:
四-3-1 - 另外一种常用的 2D/1D 的方程为:
- d [ i ] [ j ] = o p t ( d [ i − 1 ] [ k ] + w ( i , j , k ) ∣ k < j ) d[i][j] = opt( d[i-1][k] + w(i, j, k) | k < j ) d[i][j]=opt(d[i−1][k]+w(i,j,k)∣k<j)
-
- 区间模型的详细内容可以参考以下这篇文章:夜深人静写算法(二十七)- 区间DP
4、2D/2D
- d [ i ] [ j ] = o p t ( d [ i ′ ] [ j ′ ] + w ( i ′ , j ′ , i , j ) ∣ 0 < = i ′ < i , 0 < = j ′ < j ) d[i][j] = opt( d[i'][j'] + w(i', j', i, j) | 0 <= i' < i, 0 <= j' < j) d[i][j]=opt(d[i′][j′]+w(i′,j′,i,j)∣0<=i′<i,0<=j′<j)
- 如图四-4-1所示:
四-4-1 - 常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
- 对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是 O ( n t + e ) O(n^ {t+e}) O(nt+e),空间复杂度是 O ( n t ) O(n^t) O(nt) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。
- 关于 动态规划入门 的内容到这里就结束了。
- 如果还有不懂的问题,可以 想方设法 找到作者的微信进行在线咨询。
五、动态规划题集整理
1、递推
题目链接 | 难度 | 解法 |
---|---|---|
Recursion Practice | ★☆☆☆☆ | 几个初级递推 |
Put Apple | ★☆☆☆☆ | |
Tri Tiling | ★☆☆☆☆ | 【例题1】 |
Computer Transformation | ★☆☆☆☆ | 【例题2】 |
Train Problem II | ★☆☆☆☆ | |
How Many Trees? | ★☆☆☆☆ | |
Buy the Ticket | ★☆☆☆☆ | |
Game of Connections | ★☆☆☆☆ | |
Count the Trees | ★☆☆☆☆ | |
Circle | ★☆☆☆☆ | |
Combinations, Once Again | ★★☆☆☆ | |
Closing Ceremony of Sunny Cup | ★★☆☆☆ | |
Rooted Trees Problem | ★★☆☆☆ | |
Water Treatment Plants | ★★☆☆☆ | |
One Person | ★★☆☆☆ | |
Relax! It’s just a game | ★★☆☆☆ | |
Minimum Heap | ★★★☆☆ | |
N Knight | ★★★☆☆ | |
Connected Graph | ★★★★★ | 楼天城“男人八题”之一 |
2、记忆化搜索
题目链接 | 难度 | 解法 |
---|---|---|
Function Run Fun | ★☆☆☆☆ | |
FatMouse and Cheese | ★☆☆☆☆ | 经典迷宫问题 |
Cheapest Palindrome | ★★☆☆☆ | |
A Mini Locomotive | ★★☆☆☆ | |
Millenium Leapcow | ★★☆☆☆ | |
Unidirectional TSP | ★★☆☆☆ | |
Honeycomb Walk | ★★☆☆☆ | 利用记忆化简化递推 |
Brackets Sequence | ★★★☆☆ | 经典记忆化 |
Chessboard Cutting | ★★★☆☆ | 《算法艺术和信息学竞赛》例题 |
Number Cutting Game | ★★★☆☆ |
3、最长单调子序列
题目链接 | 难度 | 解法 |
---|---|---|
Constructing Roads In JG Kingdom | ★★☆☆☆ | |
Stock Exchange | ★★☆☆☆ | |
Wooden Sticks | ★★☆☆☆ | |
Bridging signals | ★★☆☆☆ | |
BUY LOW, BUY LOWER | ★★☆☆☆ | 要求需要输出方案数 |
Longest Ordered Subsequence | ★★☆☆☆ | |
Crossed Matchings | ★★☆☆☆ | |
Jack’s struggle | ★★★☆☆ | 稍微做点转化 |
4、最大M子段和
题目链接 | 难度 | 解法 |
---|---|---|
Max Sum | ★☆☆☆☆ | 最大子段和 |
Max Sum Plus Plus | ★★☆☆☆ | 最大M子段和 |
To The Max | ★★☆☆☆ | 最大子矩阵 |
Max Sequence | ★★☆☆☆ | 最大2子段和 |
Maximum sum | ★★☆☆☆ | 最大2子段和 |
最大连续子序列 | ★★☆☆☆ | 最大子段和 |
Largest Rectangle in a Histogram | ★★☆☆☆ | 最大子矩阵变形 |
City Game | ★★☆☆☆ | 最大子矩阵扩展 |
Matrix Swapping II | ★★★☆☆ | 最大子矩阵变形后扩展 |
5、线性模型
题目链接 | 难度 | 解法 |
---|---|---|
Skiing | ★☆☆☆☆ | |
Super Jumping! Jumping! Jumping! | ★☆☆☆☆ | |
Milking Time | ★★☆☆☆ | 区间问题的线性模型 |
Computers | ★★☆☆☆ | 【例题4】 |
Bridge over a rough river | ★★★☆☆ | |
Crossing River | ★★★☆☆ | |
Blocks | ★★★☆☆ | |
Parallel Expectations | ★★★★☆ | 线性模型黑书案例 |
6、区间模型
题目链接 | 难度 | 解法 |
---|---|---|
Palindrome | ★☆☆☆☆ | |
See Palindrome Again | ★★★☆☆ |
7、背包问题
题目链接 | 难度 | 解法 |
---|---|---|
饭卡 | ★☆☆☆☆ | 01背包 |
I NEED A OFFER! | ★☆☆☆☆ | 概率转化 |
Bone Collector | ★☆☆☆☆ | 01背包 |
最大报销额 | ★☆☆☆☆ | 01背包 |
Duty Free Shop | ★★☆☆☆ | 01背包 |
Robberies | ★★☆☆☆ | |
Piggy-Bank | ★☆☆☆☆ | 完全背包 |
Cash Machine | ★☆☆☆☆ | 多重背包 |
Coins | ★★☆☆☆ | 多重背包,楼天城“男人八题”之一 |
I love sneakers! | ★★★☆☆ | 背包变形 |
8、状态压缩模型
题目链接 | 难度 | 解法 |
---|---|---|
ChessboardProblem | ★☆☆☆☆ | 比较基础的状态压缩 |
Number of Locks | ★☆☆☆☆ | 简单状态压缩问题 |
Islands and Bridges | ★★☆☆☆ | |
Tiling a Grid With Dominoes | ★★☆☆☆ | 骨牌铺方格 4XN的情况 |
Mondriaan’s Dream | ★★☆☆☆ | |
Renovation Problem | ★★☆☆☆ | 简单摆放问题 |
The Number of set | ★★☆☆☆ | |
Tetris Comes Back | ★★☆☆☆ | 纸老虎题 |
Hardwood floor | ★★★☆☆ | |
Bugs Integrated, Inc. | ★★★☆☆ | 三进制状态压缩鼻祖 |
Another Chocolate Maniac | ★★★☆☆ | 三进制 |
Emplacement | ★★★☆☆ | 类似Bugs那题,三进制 |
Toy bricks | ★★★☆☆ | 四进制, 左移运算高于& |
Quad Tiling | ★★★☆☆ | 骨牌铺方格 4XN的情况 利用矩阵优化 |
Eat the Trees | ★★★☆☆ | 插头DP入门题 |
Formula 1 | ★★★☆☆ | 插头DP入门题 |
The Hive II | ★★★☆☆ | 插头DP |
Plan | ★★★☆☆ | 插头DP |
Manhattan Wiring | ★★★☆☆ | 插头DP |
Pandora adventure | ★★★★☆ | 插头DP |
Tony’s Tour | ★★★★☆ | 插头DP,楼天城“男人八题”之一 |
Pipes | ★★★★☆ | 插头DP |
circuits | ★★★★☆ | 插头DP |
Beautiful Meadow | ★★★★☆ | 插头DP |
I-country | ★★★★☆ | 高维状态表示 |
Permutaion | ★★★★☆ | 牛逼的状态表示 |
01-K Code | ★★★★☆ | |
Tour in the Castle | ★★★★★ | 插头DP(难) |
The Floor Bricks | ★★★★★ | 四进制(需要优化) |
9、树状模型
题目链接 | 难度 | 解法 |
---|---|---|
Anniversary party | ★☆☆☆☆ | 树形DP入门 |
Strategic game | ★☆☆☆☆ | 树形DP入门 |
Computer | ★★☆☆☆ | |
Long Live the Queen | ★★☆☆☆ | |
最优连通子集 | ★★☆☆☆ | |
Computer Network | ★★☆☆☆ | |
Rebuilding Roads | ★★★☆☆ | 树形DP+背包 |
New Year Bonus Grant | ★★★☆☆ | |
How Many Paths Are There | ★★★☆☆ | |
Intermediate Rounds for Multicast | ★★★★☆ | |
Fire | ★★★★☆ | |
Walking Race | ★★★★☆ | |
Tree | ★★★★★ | 树形DP,楼天城“男人八题”之一 |
10、滚动数组优化常见问题
题目链接 | 难度 | 解法 |
---|---|---|
Palindrome | ★☆☆☆☆ | |
Telephone Wire | ★☆☆☆☆ | |
Gangsters | ★☆☆☆☆ | |
Dominoes | ★☆☆☆☆ | |
Cow Exhibition | ★☆☆☆☆ | |
Supermarket | ★★☆☆☆ |
11、决策单调性
题目链接 | 难度 | 解法 |
---|---|---|
Print Article | ★★★☆☆ | |
Lawrence | ★★★☆☆ | |
Batch Scheduling | ★★★☆☆ | |
K-Anonymous Sequence | ★★★☆☆ | |
Cut the Sequence | ★★★☆☆ | |
Easy Climb | ★★★☆☆ | |
MAX Average Problem | ★★★☆☆ |
12、常用优化
题目链接 | 难度 | 解法 |
---|---|---|
Divisibility | ★★☆☆☆ | 利用同余性质 |
Magic Multiplying Machine | ★★☆☆☆ | 利用同余性质 |
Moving Computer | ★★☆☆☆ | 散列HASH表示状态 |
Post Office | ★★★☆☆ | 四边形不等式 |
Minimizing maximizer | ★★★☆☆ | 线段树优化 |
Man Down | ★★★☆☆ | 线段树优化 |
So you want to be a 2n-aire? | ★★★☆☆ | 期望问题 |
Expected Allowance | ★★★☆☆ | 期望问题 |
Pollution | ★★★☆☆ | 期望问题 |
Greatest Common Increase Subseq | ★★★☆☆ | 二维线段树优化 |
Traversal | ★★★☆☆ | 树状数组优化 |
Find the nondecreasing subsequences | ★★★☆☆ | 树状数组优化 |
Not Too Convex Hull | ★★★★☆ | 利用凸包进行状态转移 |
In Action | ★★★☆☆ | 最短路+背包 |
Search of Concatenated Strings | ★★★☆☆ | STL bitset 应用 |
Chopsticks | ★★★☆☆ | 经典筷子问题 |
13、其他类型的动态规划
题目链接 | 难度 | 解法 |
---|---|---|
Common Subsequence | 2D/0D | |
Advanced Fruits | 2D/0D | |
Travel | 2D/1D | |
RIPOFF | 2D/1D | |
Balls | 2D/1D | |
Projects | 2D/1D | |
Cow Roller Coaster | 2D/1D | |
LITTLE SHOP OF FLOWERS | 2D/1D | |
Pearls | 2D/1D | |
Spiderman | 2D/0D | |
The Triangle | 2D/0D | |
Triangles | 2D/0D | |
Magazine Delivery | 3D/0D | |
Tourist | 3D/0D | |
Rectangle | 2D/1D | |
Message | 2D/1D | |
Bigger is Better | 2D/1D | |
Girl Friend II | 2D/1D | |
Phalanx | 2D/1D | |
Spiderman | 最坏复杂度O(NK),K最大为1000000,呵呵 | |
Find a path | 3D/1D 公式简化,N维不能解决的问题试着用N+1维来求解 |