专栏文章

论 DP

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mio3vwqa
此快照首次捕获于
2025/12/02 12:56
3 个月前
此快照最后确认于
2025/12/02 12:56
3 个月前
查看原文
本文大约 3×1033 \times 10^3 个字,阅读起来大约需要 1010 分钟。
DP 题目一般都是有 DP 的状态,状态转移方程和答案组成,我们每个 DP 的例题都会进行详解。
【例 1】(01 背包问题)你有 nn 个物品和一个载重为 mm 的背包,第 ii 个物品的重量为 wiw_i,价值为 viv_i,求在重量限制下的价值最大和。
针对 30%30\% 的数据:n,m20n,m \le 20
针对 50%50\% 的数据:n,m50n,m \le 50
针对 100%100\% 的数据:n1000,m10000n \le 1000,m \le 10000
算法 1:
我会搜索(或我会状压)!考虑直接爆搜,选择每个物品是否选,再判断是否满足重量限制,时间复杂度 O(2n)O(2^n),可以通过 3030 分。
算法 2:
我会剪枝!考虑剪枝,如果当前的重量 >m>m,那么就剪枝。这种叫可行性剪枝,如果最小的重量也大于 mm,那么直接输出 00,可能通过 5050 分。
算法 3:
我会 DP!我们设 dpi,jdp_{i,j} 表示到第 ii 个数,背包重量为 jj 时的最大价值和,则 dpi,j=dpi1,kdp_{i,j}=dp_{i-1,k}j<wij<w_i),dpi,j=max(dpi1,j+dpi1,jwi)dp_{i,j}=\max(dp_{i-1,j}+dp_{i-1,j-w_i})jwij \ge w_i)。时间复杂度 O(nm)O(nm),可以通过满分。
【例 2】(最长上升子序列)一个序列 a1,a2,a3,,ana_1,a_2,a_3,\cdots,a_n,选出其子序列 bab∈a,且 b1<b2<...<blb_1<b_2<...<b_l,求 ll 的最大值。
针对 40%40\% 的数据:n20n \le 20
针对 100%100\% 的数据:n10000n \le 10000
算法 1:我会搜索!直接判断每个数选不选。
算法 2:我会 DP!假设 ii 的上一个数为 jjj<ij<i,则 aj<aia_j<a_idpi=dpj+1dp_i=dp_j+1
时间复杂度 O(n2)O(n^2)
思考题 n106n \le 10^6
【例 3】(最长上升子序列计数)一个序列 a1,a2,a3,,ana_1,a_2,a_3,\cdots,a_n,选出其子序列 bab∈a,且 b1<b2<...<blb_1<b_2<...<b_l,当 ll 取最大值时有多少种方案。
针对 100%100\% 的数据:n10000n \le 10000,答案在 long long 范围内。
很容易计算最长上升子序列长度,然后计数就是当 dpi=dpj+1dp_{i}=dp_{j}+1fi=fjf_{i}=\sum f_{j}
答案即为 fnf_n。时间复杂度 O(n2)O(n^2)
思考题 n106n \le 10^6
【例 4】(最长公共子序列)有两个字符串 s,ts,tus,vtu∈s,v∈tu=vu=v,则称 u,vu,v 为字符串 s,ts,t 的公共子序列,求最长的公共子序列长度。
针对 10%10\% 的数据:s10,t10|s| \le 10,|t| \le 10
针对 30%30\% 的数据:s20,t20|s| \le 20,|t| \le 20
针对 100%100\% 的数据:s2000,t2000|s| \le 2000,|t| \le 2000
算法 11:我会暴力!直接枚举所有 s,ts,t 的子序列,询问是否相等,时间复杂度 O(2s+t)O(2^{|s|+|t|}),可以通过 1010 分。
算法 22:我会优化!将 tt 的子序列存入 set 中,然后每次判断是否 ss 的子序列在 tt 中存在,时间复杂度 O(t(2s+2t))O(|t|(2^{|s|}+2^{|t|})),可以通过 3030 分。
算法 33:我会 DP!设 fi,jf_{i,j} 表示 ss 截止第 ii 位,tt 截止第 jj 位的最长公共子序列,则答案为 fs,tf_{|s|,|t|}。状态转移:当 ss 的第 ii 位,tt 的第 jj 位相等时,变成 fi1,j1+1f_{i-1,j-1}+1,否则是在 fi1,jf_{i-1,j}(即 ss 串的上一位)和 fi,j1f_{i,j-1}(即 tt 串的上一位)中取较大的。
时间复杂度 O(st)O(|s||t|)
思考题:s2×105,t2×105|s| \le 2 \times 10^5,|t| \le 2 \times 10^5
【例 5】(乘积最大)一个长度为 NN 的数字串,用 KK 个乘号分割,求分割成 K+1K+1 个部分的乘积的最大值。
针对 30%30\% 的数据:1N18,1K41 \le N \le 18,1 \le K \le 4
针对 100%100\% 的数据:1N40,1K61 \le N \le 40,1 \le K \le 6
算法 1:我会暴力!直接爆搜每个乘号的位置,时间复杂度 O(NK)O(N^K),不可以通过。
算法 2:我会 DP!设 fi,jf_{i,j} 表示截止第 ii 位用了 jj 个乘号的最大乘积,则设上一个乘号用在 ll 上,则 1lj11 \le l \le j-1(显然),状态转移 fi,j=max(fl,j×sl+1,i)f_{i,j}=\max(f_{l,j} \times s_{l+1,i}),其中 si,js_{i,j} 表示第 ii 位到第 jj 位所构成的数,注意会爆 long long/__int128,所以需要高精加、高精乘低精,高精乘高精,时间复杂度 O(N4K)O(N^4K),瓶颈在于高精乘高精,若用 FFT 优化可以达到 O(KN3logN)O(KN^3 \log N),可以通过 N100,K20N \le 100,K \le 20 的数据。
上面几道例题都是普通 DP,下面我们正式开始讲 DP 优化。DP 优化有几个部分:(1)单调队列优化(2)数据结构优化(3)斜率优化(超过 S 组难度,不讲)。(4)滚动数组优化(5)前缀优化。
我们先讲单调队列优化。
【例 6】(选点问题)你有 nn 个点,每个点都有价值 viv_i,选择任意 mm 个点(mm 不固定)a1,a2,,ama_1,a_2,\cdots,a_m,使得对于任意的 2im2 \le i \le maiai1ka_i-a_{i-1} \le k,求 i=1mvi\sum_{i=1}^m v_i 的最大值。
针对 10%10\% 的数据:1kn201 \le k \le n \le 20
针对 50%50\% 的数据:1kn1041 \le k \le n \le 10^4
针对 100%100\% 的数据:1kn1061 \le k \le n \le 10^6
算法 1:我会暴力!直接暴力枚举所有选的点(枚举子集),然后判断是否满足条件,时间复杂度 O(2n)O(2^n)
算法 2:我会 DP!设 fif_i 表示选择第 ii 个点的情况下,最大的价值和,则转移方程 fi=maxj=min(ik,0)i1fj+vif_i=\max_{j=\min(i-k,0)}^{i-1} f_j+v_i,时间复杂度 O(nk)O(nk)
算法 3:我会 DP 优化!注意到 fif_i 转移的前一项就是一个长 kk 的滑动窗口,所以只需要 O(n)O(n) 就可以处理,DP 的时候也是 O(n)O(n),时间复杂度 O(n)O(n)
数据结构优化也是很常用的方法。主要是依靠 O(nlogn)O(n \log n) 的数据结构优化 DP,如线段树,BIT 等,但目前没有找到合适的例题。
接下来是滚动数组优化。
【例 7】同例 4,不过 s3×104,t3×104|s| \le 3 \times 10^4,|t| \le 3 \times 10^4 且空间 512 M,时间 3s。
注意到 O(st)O(|s||t|) 的算法还是勉强可以过的,但是空间直接爆掉,注意到每个数只依赖于上、左、斜上三个数,所以每次只要开一个 2×t2 \times |t| 的数组就可以了,转移方程也可以这样推,所以就可以通过这题。
这种方法就是滚动数组优化。
【例 8】(最大子段和)给出一个 nnnn 个数的序列,求 L,RL,R 使得 aL+aL+1++aRa_L+a_{L+1}+\cdots+a_R 最大,求出这个最大值即可。(各种优化)
针对 10%10\% 的数据:n100n \le 100
针对 50%50\% 的数据:n5000n \le 5000
针对 100%100\% 的数据:1n2×1061 \le n \le 2 \times 10^6xi109|x_i| \le 10^9
算法 1:我会暴力!直接暴力枚举 [L,R] 并再用一重循环加上他们的和,时间复杂度 O(n3)O(n^3),拿到 10pts10pts
算法 2:我会前缀优化优化暴力!直接计算出每个数的前缀和并拿 sRsL1s_R-s_{L-1} 取最大值,时间复杂度 O(n2)O(n^2),拿到 50pts50pts
算法 3:我会 DP!考虑 fif_i 表示取第 ii 个数的最大值,则答案等于 max(fi)\max(f_i),考虑转移:fif_i 应该是只取 aia_i 或取 ai1a_{i-1} 这两种可能,所以状态转移就是 fi=max(fi1+ai,ai)f_i=\max(f_{i-1}+a_i,a_i),时间复杂度 O(n)O(n)
还有一些其他的 DPDP,例如树形 DP,状压 DP。
状压 DP 有一道例题。
【例 9】(公司问题)已知 NN 个点 MM 条边的图和 PP 家公司,第 ii 条边用 [L,R,W,k][L,R,W,k] 表示,表示从第 LL 个点到第 RR 个点有一条边的权值是 WW,且属于第 kk 家公司,有 QQ 个问询,询问从 uuvv 经过最多 kk 家公司的边的最短路,若走不到则输出 "NO WAY"。
针对 50%50\% 的数据:P=1P=1
针对 100%100\% 的数据:N104,M104,P6,Q2×105N \le 10^4,M \le 10^4,P \le 6,Q \le 2 \times 10^5
对于 P=1P=1,可以直接最短路。
如果 P1P≠1,那么考虑状压每个经过公司的状态,转移方程为 dpi,v,S(2k)=min(dpi,v,S(2k),dpi,u,S+w)dp_{i,v,S|(2^k)}=\min(dp_{i,v,S|(2^k)},dp_{i,u,S}+w),其中 kk 为当前边属于哪家公司,SS 为状态,vvuu 的邻居,dpi,j,Sdp_{i,j,S} 表示从 iijjSS 集合公司的最短路,最后求的时候直接暴力求出所有二进制位下 11 个数 k\le k 的个数。
时间复杂度 O(2Pmlog2Pm+q2P)O(2^Pm \log 2^Pm+q2^P)
树形 DP 同样有一道例题。
【例 10】(独立集问题)一颗树,根为 rtrt,共 nn 个结点,每个点都有点权,若 (u,v)(u,v) 有边则 u,vu,v 之间最多选一个,求最大点权和。
针对 20%20\% 的数据:n20n \le 20
针对 100%100\% 的数据:n105n \le 10^5
算法 11:我会暴力!直接暴力枚举所有子集,判断是不是独立集,然后计算点权和的最大值。
算法 22:我会 DP!记 dpu,0dp_{u,0} 表示不选 uu 的最大点权和,dpu,1dp_{u,1} 表示选 uu 的最大点权和,容易推出转移方程 dpu,1=au+vsonudpv,0dp_{u,1}=a_u+\sum_{v∈son_u} dp_{v,0}dpu,0=vsonudpv,0+dpv,1dp_{u,0}=\sum_{v∈son_u} dp_{v,0}+dp_{v,1},答案即是 max(dprt,0,dprt,1)\max(dp_{rt,0},dp_{rt,1}),其中 aua_uuu 号的点权。
时间复杂度 O(n)O(n)
【例 11】(合并石子)共有 nn 堆石子,并成一堆,每次相邻合并的代价为两堆石子质量之和,求最小合并代价。
针对 20%20\% 的数据:n10n \le 10
针对 100%100\% 的数据:n500n \le 500
算法 1:我会暴力!直接暴力枚举合并顺序,时间复杂度 O(n!)O(n!)
算法 2:注意到 n500n \le 500,考虑 O(n3)O(n^3) 的 DP。DP 分类中有一种叫区间 DP 的,时间复杂度都是 O(n2)O(n^2)O(n3)O(n^3) 的。注意到 [i,k][i,k][k+1,j][k+1,j] 合并的代价为 sumi,jsum_{i,j}sumi,jsum_{i,j} 代表 iijj 的质量之和,于是转移方程为 fi,j=mink=ijfi,k+fk+1,j+sumi,jf_{i,j}=\min_{k=i}^j f_{i,k}+f_{k+1,j}+sum_{i,j}
时间复杂度 O(n3)O(n^3)
【例 12】(回文串分割)你现在有一个长度为 nn 的字符串,想将其分成回文串的组合。请问你至少要分出几个字符串?
n3000n \le 3000
这个题目,设 fif_i 为截止到 ii 的分出字符串最少个数,则 fi=minfj+1f_i=\min f_j+1,其中 [j,i][j,i] 为回文串。回文串可以进行两边哈希,然后就可以 O(1)O(1) 求了。
时间复杂度 O(n2)O(n^2)
【例 13】(异或和最大分割)你现在有一个长度为 nn 的序列,想分割为一系列序列,使得其异或和之和最大,求这个最大值。空间限制 128MB。
针对 10%10\% 的数据:n10n \le 10
针对全部数据:n8000n \le 8000
暴力做法是枚举所有的分割点,时间复杂度可能达到 O(n!)O(n!)
fif_i[1,i][1,i] 的分割分出的异或和最大值,则 fi=fj+sumj,if_i=f_j+sum_{j,i},显然可以 O(n2)O(n^2) 预计算 sumj,isum_{j,i},但空间需要 250MB。
观察到异或的一个性质:[l,r][l,r] 的异或和 = [1,r][1,r] 的异或和 异或 [1,l1][1,l-1] 的异或和,维护前缀异或和即可,时间复杂度 O(n2)O(n^2),空间复杂度 O(n)O(n)
【例 14】(最长回文串)给定一个长度为 nn 的字符串,求其最长回文子串。
n6000n \le 6000
针对 30%30\% 的数据:n500n \le 500
首先有一个 O(n3)O(n^3) 的做法。
这个题很明显是 O(n2)O(n^2) 的 DP 题,记 fl,rf_{l,r}[l,r][l,r] 是否是回文串,则 fi,i=1f_{i,i}=1,若 ai=ai+1a_i=a_{i+1}fi,i+1=1f_{i,i+1}=1 否则 fi,i+1=0f_{i,i+1}=0。然后再算 fl,rf_{l,r},若 sl=srs_l=s_rfl,r=fl+1,r1f_{l,r}=f_{l+1,r-1},否则等于 00
然后算答案枚举 lenlenll,然后求出 rr 和对应 DP 值,并判断是否是 11 即可。当然这题还可以双指针和哈希。
时间复杂度 O(n2)O(n^2)
【例 15】(最长上升子序列)一个序列 a1,a2,a3,,ana_1,a_2,a_3,\cdots,a_n,选出其子序列 bab∈a,且 b1<b2<...<blb_1<b_2<...<b_l,求 ll 的最大值。
针对 40%40\% 的数据:n10000n \le 10000
针对全部数据:n106n \le 10^6
算法 1:我会暴力 DP,直接 O(n2)O(n^2) DP 上,可以拿到 40pts。
算法 2:观察该题的 DP 方程,显然可以进行权值线段树优化,j<ij<iaj<aia_j<a_i,我们考虑在 aj<aia_j<a_i 上做操作。我们每次在 aia_i 上加上 fif_i,然后每次求就是求区间最大值,只要会单点修改区间求最大值的模板就可以了。
时间复杂度 O(nlogn)O(n \log n)。如果值域比较大,可以进行动态开点。

评论

0 条评论,欢迎与作者交流。

正在加载评论...