经典例题
例1:[NOIP1999 普及组] 导弹拦截
【思路点拨】
(1)一套拦截系统最多可以拦截多个导弹,就是求一个序列的最长不上升子序列,暴力 O(N2) 会超时,利用二分优化到 O(nlogn) (经典模型里有思路)。
(2)拦截所有导弹最少的拦截系统数量,可以贪心去求,用一个数组记录每一个系统拦截的最低高度,新来一个导弹,应该优先让能够拦截并且高度最低系统拦截(高度高的系统有能力拦截更高的),如果都无法拦截就新增加一套。复杂度是 O(N2) 。实际本问就是求最长上升子序列长度,那么二分优化 LIS 就可以。
参考代码:
偏序,按照 L 从小到大排序,如果 W 递减,就不需要停下来准备,也就是分成最少的几条 W 下降的链,根据Dilworth 定理就等于最长的反链长度,也就是 W 的严格上升子序列长度。
变形例题:P1439 【模板】最长公共子序列
subtask1:n≤103
可以使用 LCS 普通模型完成,设 F[i,j] 表示前缀子串 A[1...i] 与 B[1...j] 的“最长公共子序列”的长度,时间复杂度 O(nm)。
subtask2:n≤105
要巧用 1…n 的排列这个限制信息,对于第一个排列A[i] ,可以再 B[] 中找到其对应的位置 P[i],即P[i] 表示 A[i] 在 B[] 中的位置,那么 P[i] 中最长上升序列就是答案。
画出图来,i 是递增的,如果 P[i] 也递增,那么就不会交叉,不交叉的数量越多越好,这就是 LIS .

例2:LCIS(最长公共上升子序列)
题意:对于两个数列 A 和 B ,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列。
subtask1:n≤500,m≤500
【解析】:F[i,j] 表示 A[1...i] 与 B[1...j] 可以构成的 以Bj 为结尾 的 LCIS 的长度。
当 Ai=Bj 时,F[i,j]=F[i−1,j]
当 Ai=Bj 时, F[i,j]=max0≤k<j,Bk<Bj{F[i−1,k]}+1=F[i,j]=max0≤k<j,Bk<Ai{F[i−1,k]}+1
上面的转移过程可以用三重循环进行计算,时间复杂度为 O(nm2)。
对于例题CF10D LCIS,求LCIS长度,还要输出一个LCIS方案.
参考代码:
subtask2:n≤3000,m≤3000
数据规模扩大,需要进行优化处理。
在第二层循环 j 从 1 增大到 m 时,第一层循环 i 是一个定值,这使得条件 Bk<Ai 是固定的。因此,当变量 j 增加 1 时,k 的取值范围从 0≤k<j 变为 0≤k<j+1,即整数 j 可能会进入新的决策集合。
也就是我们只需要 O(1) 地检查条件 Bj<Ai 是否满足,已经在决策集合中的数则一定不会被去除。
我们把满足 0≤k<j,Bk<Ai 的 k 构成的集合称为 F[i,j] 进行状态转移时的决策集合,记为 S(i,j)。
S(i,j+1)={S(i,j),S(i,j)∪{j},ififBj>=AiBj<Ai
上面的转移只要两重循环就可以求解,最终目标为 max(F[n,j]),1≤j≤m。
在实现状态转移方程时,要注意观察决策集合的范围随着状态的变化情况。
对于“决策集合中的元素只增不减”的情景,就可以像本题一样维护一个变量来记录决策集合的当前信息,避免重复扫描,把转移复杂度降低一个量级。
题意简化 :给定 k 个任务,每个任务持续时间为 [pj,pj+t−1],时刻 i 如果有任务尼克可以选择一个任务,如果时刻 i 没有任务,尼克可以空闲,询问尼克 [1,n] 时间段内最长休息时间。 1≤n,k≤104
分析: 可以将任务按照开始时刻分类(排序),如果时刻 i 没有任务那就休息,如果有任务那可以枚举此时刻开始的所有任务,保留最大值。
那么可以定义状态:fi 表示时间 [i...n] 最大休息时间。
- 如果时刻 i 没有任务: fi=fi+1+1
- 枚举时刻 i 开始的所有任务 j (pj==i),保留最优值: fi=max(fpj+tj)
转移是时间序列上线性的,按时间从大到小,线性dp基本模型。初始 fi=0 ; 结果ans=f1。
每个任务只会被枚举一次,时间复杂度为 O(n+k).
参考代码:
题意简化:给定 H 的小时,n 个鱼塘,第 i 个鱼塘从开始钓鱼,开始每 5min 钓 fi,每过 5min 钓鱼量减少 di,依次递减。从第 i 个鱼塘走到第 i+1 个鱼塘需要 5∗tizhuangt第一个鱼塘,最后可以停在任意一个鱼塘。询问H$ 小时的能钓到最多的鱼。 1≤H≤16,2≤n≤25
分析
题目中都是以 5min 作为最小计算单位时间计算的,那么把所有时间都转化为多少个 5min,H→12∗H,最优性问题常见的解决思路,就是贪心(问题本身要符合全局最优”)、动态规划(问题能够定义成分阶段“最优子结构”的形式)。
解法1:贪心
本质上,就是除了路上花的时间外,把时间分配到能钓到鱼多的鱼塘。枚举最后停留的鱼塘 i,除去路上花的时间,每一个 5min,选择 1−i 鱼塘的鱼量最多的鱼钓鱼,直到时间用完,或没有鱼了。 选择当前最多鱼塘,可以使用大根堆进行优化。
枚举鱼塘 O(n),时间依次递减 O(12h),每次选择最多的鱼的鱼塘(堆优化)O(logi),那么整体时间复杂度就是 O(n×12h×logn).
参考代码:
解法2:动态规划
前 i 个鱼塘,考虑第 i 个鱼塘给分配多少个 5min,可以调到最多的鱼,给前 i 个鱼塘分配 j 个 5min, i 是阶段,j 看做“资源”(“资源规划使得最优”),本质上是“泛化”的背包问题。
那么定义状态:dp[i][j] 前i个湖停留 j 个 5分钟 所能钓到最多的鱼的数量
给第 i 个鱼塘分配 k 个 5分钟, 第 i 个鱼塘能钓到的鱼就是 fi+(fi−di)+...+(fi−(k−1)∗di)=2(2∗fi−(k−1)∗di)∗k,当然 fi−(k−1)∗di>=0,否则会钓到负数鱼数量,肯定就不对了。
即得到 dp 转移方程 :dp[i][j]=max(dp[i−1][j−ti−1−k]+k∗(2∗fi−(k−1)∗di)/2),fi−(k−1)∗di>=0
dp[i][j] 初始化为0,答案就是: max(dp[i][j])
时间复杂度: O(n∗12h∗12h)
参考代码:
题意简化:给定一个 n∗m 二维数字网格,从起点 (1,1) 向下、向右走到终点,再从终点走到起点(方向相反),路径不相交,求经过路径上数字和的最大值。
分析: 首先“贪心走一条路,再走另外一条路” 是错误的,因为:第一路可能最大,第二天路小,而有可能两条都比较大,但总和更大。
可以两条路一起从起点走到终点,那么定义: f[x1][y1][x2][y2] 表示第一条走到 (x1,y1) ,第二条路走到 (x2,y2) 经过的数字和最大。发现状态有冗余,因为 x1+y1=x2+y2 ,那么可以优化掉一维状态
题意简化: 给定 n 个村庄的位置(线性),建 m 所学校,求所有村到附近一所的距离和的最小值。
分析: 如果只建 1 所学校,根据中位数的性质,学校应该建在 n 所学校中位数的位置; 如果建 2 所学校,可以枚举断点 i ,[1,i] 和 [i+1,n] 各建一所学校,各自建在中位数处,如果暴力求时间复杂度为 O(N2) ,可以利用“前缀和”优化,时间复杂度优化到 O(N),例题“Boyacoding "P1006. 建学校”
当 m>2 就需要利用动态规划求解。
定义 f[i][j] 表示前 i 个村庄建 j 所学校总距离最小值
f[i][j]=min{f[k][j−1]+cost(k+1,i)},其中 cost(k+1,i) 表示第 k+1 至 i 建一所学校的最小花费。
时间复杂度为 O(n2m).
学习完毕