状态设计优化

概述

优化 dp 时,不止可以从转移过程入手,加速转移.有时,也可以从状态定义入手,通过改变设计状态的方式实现复杂度上的优化.

令人比较头疼的是,这类优化大多不具有通用性,即不能很套路地应用于多个题目中.因此,下文将从具体例题出发,力求提供思路上的启发,希望可以对读者有一定帮助.

例 1

题面

给定两个长度分别为 n,mn,m 且仅由小写字母构成的字符串 A,BA,B, 求 A,BA,B 的最长公共子序列.(n≤106,m≤103)(n\le 10^6,m\le 10^3)

朴素的解法

您一眼秒了它,这不是板子吗?

定义状态 fi,jf_{i,j} 为 AA 的前 ii 位与 BB 的前 jj 位最长公共子序列,则有

fi,j={max⁡(fi−1,j,fi,j−1),Ai≠Bjfi−1,j−1+1,Ai=Bjf_{i,j}= \begin{cases} \max(f_{i-1,j},f_{i,j-1}) & ,A_i \neq B_j \\ f_{i-1,j-1}+1 & ,A_i = B_j \end{cases}

上述做法的时间复杂度 O(nm)O(nm),无法通过本题.

更优的解法

我们仔细一想,发现了一个性质:最终答案不会超过 mm.

我们又仔细一想,发现 LCS 满足贪心的性质.

更改状态定义 fi,jf_{i,j} 为与 BB 前 ii 位的最长公共子序列长度为 jj 的 AA 的最短前缀长度(即将朴素做法的答案与第一维状态对调)

可以通过预处理 AA 的每一位的下一个 a,b,⋯ ,za,b,\cdots,z 的出现位置进行 O(1)O(1) 的顺推转移.

复杂度 O(m2+26n)O(m^2+26n),可以通过本题.

例 2

题面

给定一个 nn 个点的无权有向图,判断该图是否存在哈密顿回路.(2≤n≤20)(2\le n\le 20)

朴素的解法

看到数据范围,我们考虑状压.

设 fs,if_{s,i} 表示从点 11 出发,仅经过点集 ss 中的点能否到达点 ii.记 gg 为原图的邻接矩阵.则有

fs,i=⋁j∈s,j≠ifs∖{i},j∧gj,i(i∈s)f_{s, i} = \bigvee_{j\in s, j\neq i}f_{s \setminus \{i\}, j}\wedge g_{j, i} \left(i\in s\right)

时间复杂度 O(n2×2n)O(n^2 \times 2^n),写得好看或许能过,但是并不优美.

更优的解法

上面的状态设计中,每个 dpdp 值只代表一个 bool 值,这让我们觉得有些浪费.

我们可以考虑对于每个状态 ss 将 fs,1,fs,2,…,fs,nf_{s,1},f_{s,2},\dots,f_{s,n} 压成一个 int,发现我们可以将邻接矩阵同样压缩后进行 O(1)O(1) 转移.

时间复杂度 O(n2/w×2n)O(n^2/w\times 2^n), 可以通过这道题,其中 ww 为 int 的位数.

例 3

题面

常规的背包问题.nn 为物品数量,mm 为背包容量,vi,wiv_i, w_i 为第 ii 个物品的体积、价值,1≤n≤1031 \le n \le 10^3,1≤m,vi≤10181 \le m, v_i \le \color{red}{10^{18}},1≤∑wi≤1031 \le \sum w_i \le 10^3.

朴素的解法

这是一个模板背包题.

定义状态 fi,jf_{i, j} 为选了前 ii 个物品,目前背包里塞了 jj 的容量的最大价值和.

易得 fi,j=max⁡(fi−1,j,fi−1,j−vi+wi)f_{i, j} = \max(f_{i - 1, j}, f_{i - 1, j - v_i} + w_i).

vi≤1018v_i \le 10^{18},无法通过此题.

更优的解法

交换答案和状态的第二维,设 fi,jf_{i, j} 为选了前 ii 个物品,目前背包里的物品 的价值为 jj 的最小体积和.

依然,易得 fi,j=min⁡(fi−1,j,fi−1,j−wi+vi)f_{i, j} = \min(f_{i - 1, j}, f_{i - 1, j - w_i} + v_i).

注意状态的第二维改变之后转移也要一起改变.

时间复杂度 O(n∑wi)O(n \sum w_i),可以通过此题.

本文内容来自 OI Wiki《状态设计优化》;作者与修订历史见原文。AIOJ 对排版、站内链接和资源路径做了格式适配。

内容依 CC BY-SA 4.0 与 SATA 许可共享;附加条款以原文声明为准。感谢 OI Wiki 作者与贡献者;请按 SATA 约定前往 OI Wiki 仓库 Star 支持。