题意:给定长度为 的数组 和一个整数 。你可以选择至多一个连续子段,将其中所有元素乘上 。求操作后的最大子段和(子段可为空,空段和为 )。
数据:,,。时限 2s。
Hint 1
模型分析
至多一段乘 → 序列被切成「普通 → 乘 → 普通」三段(任一段可为空)。
Hint 2
DP 对象与生成方式
以 结尾的最大子段和,但要额外记录当前处于第几阶段。 三状态递推。。
启示
把"是否进入过某特殊状态"显式编码为状态的一维。
题意:给定长度为 n 的数组 a1,…,an 和一个整数 x。你可以选择至多一个连续子段,将其中所有元素乘上 x。求操作后的最大子段和(子段可为空,空段和为 0)。
数据:n⩽3×105,∣ai∣⩽109,∣x∣⩽109。时限 2s。
模型分析
至多一段乘 x → 序列被切成「普通 → 乘 x → 普通」三段(任一段可为空)。
DP 对象与生成方式
以 i 结尾的最大子段和,但要额外记录当前处于第几阶段。dp[i][0/1/2] 三状态递推。O(n)。
启示
把"是否进入过某特殊状态"显式编码为状态的一维。
题意:长度为 n 的序列只含 1 和 2。允许翻转一个连续子段恰好一次。求操作后最长不降子序列的长度。
数据:n⩽2000。时限 1s。
翻转一段 = 允许序列走 1→2→1→2 的四段式。等价于在 dp[i][1..4] 上做状态机转移。
题意:长度为 N 的字符串,字符集 A~J(共计 10 种)。求有多少个子序列满足:同种字符在子序列中出现的位置必须连续(即不能出现 ABA 型穿插)。结果对 998244353 取模。
数据:N⩽1000。时限 2s。
模型转化
"不能出现 ABA" → 等价于:每种字符一旦开始选就必须连续选完,之后不能再回头选。
DP 对象与必要性推导
从左到右逐个字符决策选/不选。需要知道:上一个选的字符是谁(判断能否继续同种),以及历史上已经用过哪些字符(用过就不能再新开)。
dp[i][last][mask] = 前 i 个字符,上一位选的字符为 last(0~9),已选字符集合为 mask(二进制)。O(N⋅10⋅210)。
题意:长度为 n 的正整数序列 a1,…,an。求最长的子序列,使得相邻两个元素不互质(即 gcd>1)。
数据:n⩽105,ai⩽105。时限 2s。
模型分析
gcd>1 等价于有共同质因子。转移时不能只看上一个元素是谁——如果 ai 和 ai−1 不互质但和 ai−2 互质,照样能接在 ai−2 后面。
DP 对象的重定义(核心!)
DP 对象不是"以第 i 个元素结尾",而是以某个质因子 p 结尾。
dp[p] = 最后一个数含有质因子 p 的最长子序列长度。对每个 ai,枚举其所有质因子 p,用 maxq∣aidp[q]+1 更新所有 dp[p]。O(na) 或 O(nloga)(预处理质因子)。
启示
DP 对象不一定是你第一反应的那个。这是 Step 2 的精髓。
题意:给定两个 1∼n 的排列 P1,P2。求它们的最长公共子序列(LCS)长度。
数据:n⩽105。时限 1s,内存 125MB。
模型转化(关键!)
两个排列的 LCS 可以转化为 LIS。令 pos[x] = x 在 P2 中的位置。将 P1 的每个元素替换为 pos[P1[i]],然后在新序列上求 LIS。O(nlogn)。
启示
Step 1 的模型简化——把"两个序列的匹配"变成"一个序列的单调性"。
题意:给定一个 1∼n 的排列和整数 k。求长度为 k+1 的最长上升子序列(严格递增)的个数。
数据:n⩽105,k⩽10。时限 1s。
dp[len][pos] = 以 pos 结尾、长度为 len 的 LIS 个数。BIT(树状数组)每层维护前缀和优化转移。O(nklogn)。
题意:一套导弹拦截系统,第一发可拦截任意高度的导弹,之后每一发不能高于前一发。给定导弹高度序列,求:(1)一套系统最多拦截多少枚;(2)拦截所有导弹最少需要几套系统。
数据:导弹数量 ⩽105,高度 ⩽5×104。时限 1s。
(1)最长不升子序列(LDS),O(nlogn)。 (2)Dilworth 定理:偏序集的最小链覆盖 = 最长反链长度 → 求 LIS 长度即可。
题意:长度为 n 的字符串 s 和 t。每次操作可将 s 的任意一个字符往左移动任意步(越过前面的字符)。求最少操作次数使 s=t。若无法做到输出 −1。
数据:∑n⩽2000(多组数据)。时限 2s。
模型分析
往左移动 = 字符可以"延后匹配",即允许跳过前面的字符。若 s 和 t 的多重集不同则无解。
DP 对象
dp[i][j] = s 的前 i 个字符中,有多少个已经被匹配到了 t 的前 j 个。当 si=tj 时可向前匹配;否则要么跳过 si(往左移),要么 tj 等待后方字符。O(n2)。
题意:字符串 A 通过三种操作变成 B:插入一个字符、删除一个字符、替换一个字符。每步代价均为 1。求最小总代价。
数据:∣A∣,∣B∣⩽2000。时限 1s。
经典双序列 DP。设 Di,j 表示 A[1..i] 和 B[1..j] 的编辑距离。若 Ai=Bj 则 Di,j=Di−1,j−1;否则 Di,j=1+min(Di−1,j,Di,j−1,Di−1,j−1)。
题意:字符串 A(长 n)、B(长 m)。对于任意 1⩽i⩽n、1⩽j⩽m,令 Li,j 表示 A[1..i] 与 B[1..j] 的 LCS 长度,定义 f(i,j)=4Li,j−i−j。求 maxi,jf(i,j)。
数据:n,m⩽5000。时限 1s,内存 256MB。
dp[i][j]= 以 Ai,Bj 结尾时 4Li,j−i−j 的最大值。
转移:若 Ai=Bj,dp[i][j]=max(dp[i−1][j−1]+2,0);否则 dp[i][j]=max(dp[i−1][j],dp[i][j−1])−1。答案取 dp 表中最大值。
题意:n 个点的完全图,删去其中 m 条边。从点 1 出发,每次走到相邻点,走恰好 k 步回到任意点。求方案数,对 998244353 取模。
数据:n,m,k⩽5000。时限 2s。
正难则反。设 Ts 为第 s 步到达所有点的总方案数,Rs,v 为沿被删边走到 v 的非法方案数,则 Ds,v=Ts−1−Ds−1,v−Rs,v。Rs,v 可枚举被删边求和。O((n+m)k)。
题意:n 个玩具依次排列,第 i 个长度 ci。将玩具切分成若干连续段,每段用一个容器装。一个装了 [l,r] 的容器代价为 (r−l+∑i=lrci−L)2。求最小总代价。
原题数据:n⩽5×104,L⩽107,ci⩽107。时限 1s。
若尚未学习斜率优化,本题数据为 n⩽5000。
模型分析
玩具是有序的 → 按顺序分段即可。不需要考虑排列或子集。
DP 对象
Di = 前 i 个玩具的最小代价。转移枚举上一段的结尾 j:Di=minj(Dj+Cj,i),其中 Cj,i=(Si−Sj+i−j−1−L)2。做到这里即可,复杂度 O(n2)。
原题优化
单调队列维护下凸壳,斜率优化。O(n)。
题意:n 座塔楼排成一列,高度 hi。每次操作可将相邻两座合并(新高度 = 两高度之和)。求最少合并次数,使得最终序列非递减(每个塔楼高度 ⩽ 下一个)。
数据:n⩽5000,hi⩽105。时限 2s。
模型分析
最终每个塔楼是原序列中连续一段。塔楼数量越多,合并次数越少(答案 =n−k,k 是最终塔楼数)。目标:最大化 k。
换维(核心!)
直接记"上一段的和"作为状态量级太大。改用上一个区间的起始位置间接表示。
dp[i][j] = 末尾为 i、当前段起点为 j 时,最多能有几段。转移:枚举下一个段的结尾 k,要求 sum[j..i]⩽sum[i+1..k]。O(n2)。
题意:给定数组 a。若存在相邻且相等的两个数 ai=ai+1,可将其替换为一个 ai+1。求最终数组的最短可能长度。
数据:n⩽500,ai⩽1000。时限 2s。
初始直觉
只记 dp[l][r] = 区间 [l,r] 能合并成的最短长度。但问题来了:什么时候两段可以进一步合并成一段?
状态扩充至封闭
两段能合并 ⟺ 各自都能合并到只剩一个数,且这两个数相等。所以必须额外记录能合并到只剩一个数时,那个数是多少。
增设 b[l][r] = 区间 [l,r] 若能合并成单独一个数时的值(否则为 0)。当 dp[l][k]=dp[k+1][r]=1 且 b[l][k]=b[k+1][r] 时,dp[l][r]=1、b[l][r]=b[l][k]+1。O(n3)。
启示
状态不够就扩充,直到关于转移封闭——反复走 Step 2→3。
题意:长度为 n 的 01 串。每次操作:选择一段连续相同字符消除,得分为该段长度的对应分值 alen(a1,…,an 给定)。消除后两侧字符串拼接。求最大总分。
数据:n⩽100,ai⩽109。时限 2s。
模型分析
这题不是普通的区间 DP——因为消完左边后,右边可能和更左边的东西连在一起。单靠区间两端的信息不够。
“挂载”状态(核心!)
dp[l][r][k] = 区间 [l,r],且左边还有 k 个和 al 相同的字符等着一起消(这些字符来自更左侧,被之前的消除操作"遗留"下来)。最大得分。
转移:(1)先把这额外 k 个和 al 一起消掉;(2)在区间内找另一个和 al 相同的位置 m,把中间消掉,然后把 al 的"负载"传递给 m。O(n4)。
启示
区间 DP 有时需要让"区间外的信息"穿透边界写入状态。
题意:n 个非负整数 ai。选择一个非负整数 x,使得 maxi(ai⊕x) 最小。求这个最小值。
数据:n⩽105,ai<230。时限 1s。
DP 对象
DP 的对象不是按下标分割,而是一个"在某一位上的数字集合"。
生成方式:按位切分
从最高位(第 29 位)向下递归。设当前位为 b。若所有 ai 在第 b 位上全是 0(或全是 1),则可以选 x 的同位让答案的这一位为 0;否则无论如何这一位答案必定为 1,然后按 0/1 分成两组递归取 min。O(nlogmaxa)。
启示
分段不一定按下标——按最高位 0/1 切分也是合法的"段"。
题意:n 堆石子围成一圈,第 i 堆有 ai 颗。每次合并相邻两堆,代价 = 两堆石子数之和,合并后新堆石子数为两者之和。求最小和最大总代价。
数据:n⩽100(朴素),n⩽300(可用四边形不等式优化)。时限 1s。
断环为链(将数组复制一份接在末尾)。Dl,r=mink(Dl,k+Dk+1,r)+Sl,r。O(n3)。
题意:n 个正整数 ai。求有多少个排列 p 满足以下条件:若 pi>maxj<ipj(即 pi 刷新了前缀最大值),则必须满足 pi⩾2×maxj<ipj。结果对 998244353 取模。
数据:n⩽5000,ai⩽109。时限 2s。
模型分析
只有刷新最大值的元素有限制。非刷新元素只需要老老实实出现在某个已确定的最大值后面即可,谁先谁后无所谓。
只 DP 关键元素
将 a 升序排序。Di = 以 ai 作为最后一个"刷新最大值"的元素的方案数。转移:Di=∑Dj⋅Prem,求和范围是所有满足 2aj⩽ai 的 j,其中 Prem 表示用组合数处理中间的非关键元素。O(n2)。
题意:求 1∼n 的排列中有多少个满足 ∑i=1n∣pi−i∣=k。结果对 109+7 取模。
数据:n⩽50,k⩽n2。时限 2s。
模型分析
∑∣pi−i∣ = 直观上就是每个位置和它的值之间的"距离"。如果按值从小到大插入排列,会有一种优雅的计数方式。
开口贡献法
从小到大插入值 v=1,2,…,n。当前有 j 个"开口"(已经知道这个位置将来要放某个值,但现在还没放)。
dp[v][j][s] = 已放入值 1∼v,当前 j 个开口,已累积距离和 s 的方案数。插入 v 时有三种操作:
O(n2k)。
启示
排列 DP 不需要知道每个值去了哪个具体位置——"开口数"这种全局汇总就够了。
题意:求 1∼n 的排列中,恰好有 k 个位置满足 ∣pi−i∣=1(即"好位置",值刚好在相邻位置)的排列数。对 109+7 取模。
数据:n⩽1000,k⩽n。时限 2s。
容斥转化
"恰好 k 个"不好直接数。改为先 DP 算出"至少 j 个好位置"的方案数 f[j],再用二项式反演容斥回恰好:ans[k]=∑j=kn(−1)j−k(kj)f[j]。
DP“至少”
dp[i][j][0/1][0/1] = 考虑到位置 i,已钦定 j 个好位置,i 是否已占用、i+1 是否已占用的方案数。注意:好位置的定义是"值 i 放在位置 i−1 或 i+1"。O(n2)。
题意:n 个位置排成一行,n 个人按随机顺序依次入座。每人选一段连续的全空位置坐下,若有多段可选则等概率随机。求所有人坐下后占据的总座位数的期望(即期望最终被占据的座位数)。
数据:n⩽500。时限 2s。
模型分析
多个"极长连续被占用段"之间完全无关——因为没人会跨过空位去选另一段的座位。所以可以分别 DP 一个连续段,最后用组合数把各段合并。
最后一个人切分法(核心!)
考虑一个被占用的连续段 [l,r]。找最后一个进入该段的人,他坐在某个位置 m。他把该段切成左右两个独立的子段,分别 DP。子段间无关 → 组合数合并。
dp[i][j] = i 个位置、i 个人的段的总占用期望(含方案数),通过枚举最后一个人的位置来转移。O(n3),优化可至 O(n2)。
启示
第二类方法的典范——找"最后决策点"→ 发现独立性 → 拆分处理。
题意:n 个人分成 t 个团队(第 i 人属团队 ci)。每人必须投一票给同团队的另一个人。第 i 人恰好得 targeti 票。求合法投票方案数,对 998244353 取模。
数据:n⩽200,∑targeti=n。时限 2s。
逐团队处理。团内每个人投出的票的去向可以通过组合数算。把团内投票方案数作为物品,跨团队做背包合并。团队内部:将投票视为排列,最后除以阶乘去重。
题意:2n 人参加单淘汰赛(标准锦标赛树)。求每个人获得"木汤匙"(即所有比赛全败——说明他第一轮就遇到最终的冠军)的方案数。输出 2n 个答案。
数据:n⩽20。时限 2s。
按锦标赛层级自底向上处理。dp[mask] 从低层级向高层级合并败者集合。组合数处理层级间的选手分配。O(n2n)。
题意:n 个正整数,重新排列。求有多少种排列使相邻两数之和为完全平方数。对 109+7 取模。
数据:n⩽15,ai⩽109。时限 1s。
dp[mask][last] = 已选集合为 mask、最后一个选的是 last 的方案数。检查相邻和是否为完全平方数即可转移。O(2nn2)。
题意:数组 a1,…,an。选一个子序列(设选了 m 个,对应原下标 i1<i2<⋯<im,令 bj=aij)。价值定义为 V=S1−S2,其中 S1 是所有 bj 之和,S2 是所有连续子段最小值之和。求最大价值。
数据:n⩽4000,ai⩽109。时限 2s,内存 256MB。
在数组 a 上建笛卡尔树(以最小值为根)。选子序列等价于在笛卡尔树上选一些节点,且每个节点贡献为(自身值 × 被包含的次数)。DP 对象从排列变成了树。dp[u][j] = u 的子树中选了 j 个点的最大价值。O(n2)。
题意:n 个职员构成一棵树(上下级关系)。每个职员有快乐值 ri(可正可负)。选一些人参加舞会,要求:若一个人被选,他的直接上司不能同时被选。求最大总快乐值。
数据:n⩽6000,−128⩽ri⩽127。时限 1s。
dp[u][0/1] = 不选/选 u 时,u 的子树的最大总快乐值。选 u 时儿子都不能选;不选 u 时儿子可选可不选(取 max)。O(n)。
题意:n 个节点的树,每个节点黑色(1)或白色(0)。可以切掉任意条边。求方案数使得每个连通块中恰好有一个黑色节点。对 109+7 取模。
数据:n⩽105。时限 1s。
dp[u][0] = u 所在连通块中黑点数为 0 的方案数;dp[u][1] = 黑点数为 1 的方案数。合并儿子 v 时:若切边,v 必须自己有 1 个黑点;若不切边,v 的黑点数必须为 0。O(n)。
题意:n 门课,部分课有先修课(形成森林)。每门课有学分 si。必须选恰好 m 门课,且选了某门课必须先选它的先修课(以及先修的先修……)。求最大学分。
数据:n,m⩽300,si⩽20。时限 1s。
树上背包。加一个 0 号虚拟根把森林变成树。dp[u][j] = u 子树中选 j 门课(必须选 u)的最大学分。合并儿子:dp[u][j+k]=max(dp[u][j]+dp[v][k])。O(nm2)。
题意:n 个点的树,初始全白。第一步任选一点染黑;之后每一步选一个与已有黑点相邻的白点染黑。每次染黑的得分为:该白点当前所在白色连通块的大小。求最大总分。
数据:n⩽2×105。时限 2s。
调整法证明无关性(核心!)
关键发现:一旦选定第一步染黑的点(即根),后续操作顺序不影响总分!因为无论什么顺序,每个点染黑时的得分始终等于以根为起点时它的子树大小。
证明:交换相邻两次染色,只有当两个点在不同子树时才会互相影响——但交换前后总得分相同。
退化为选根问题
问题转化为:选根最大化子树大小之和。先 DFS 任选一个根算出初始得分,然后换根:dp[v]=dp[u]+n−2×size[v]。O(n)。
启示
Step 1 决定了整道题的难度——用调整法证明无关性,将复杂的过程问题退化为选根问题。
题意:n 个节点的树。选一个节点作为根,使所有节点深度之和(根深度为 0)最大。输出该节点编号(多个输出任意)。
数据:n⩽106。时限 1s,内存 125MB。
换根 DP 模板。先任选根(如 1)DFS 得到初始深度和 dp[1]。然后换根:当根从 u 移到儿子 v 时,v 子树内所有点深度 -1,其余点深度 +1。dp[v]=dp[u]+n−2×size[v]。O(n)。
题意:n 个点的树,每个点颜色为白(1)或黑(0)。对每个点 v,求包含 v 的连通子图中,(白点数 − 黑点数)的最大值。
数据:n⩽2×105。时限 2s。
先自底向上 DP:Du=valu+∑vmax(0,Dv),其中 v 枚举 u 的儿子(valu 为白 +1 黑 -1)。再换根:ansv=Dv+max(0,ansu−max(0,Dv))。O(n)。
题意:n 个节点的树。一步可以跳恰好 k 条边(沿着树边跳)。求所有有序点对 (u,v) 之间的最少跳跃次数之和。
数据:n⩽2×105,k⩽5。时限 2s。
模型分析
最少跳跃次数 =⌈dist(u,v)/k⌉。直接求距离和是容易的,但不能简单除以 k——因为上取整会破坏线性性。
按余数分类聚合
按 modk 的余数分类!f[u][r] = u 子树中到 u 距离 ≡r(modk) 的节点数。g[u][r] = 对应的最少跳跃次数之和。合并子树时向上传递。O(nk)。
启示
不能直接聚合的量 → 多分几类,按类分别聚合。
题意:n 个骑士,每人有一个"最讨厌的骑士" hi(可能讨厌自己)。每个骑士有战斗力 wi。选出一个骑士集合,满足集合中没有人讨厌集合中的另一个人。求最大总战斗力。
数据:n⩽106,wi⩽106。时限 1s,内存 125MB。
每个人恰讨厌一个人 → 基环树(每个连通块恰有一个环)。在环上断一条边 (u,v),做两次树形 DP:一次强制不选 u,一次强制不选 v。dp[node][0/1] 为不选/选该点的子树最大战斗力。O(n)。
题意:圆周上 n 个点按顺时针编号 1∼n。给出 n×n 的 01 矩阵 a,a[i][j]=1 表示 i 和 j 之间可以连线段(线段为弦)。求有多少种连边方案,满足:
数据:n⩽500。时限 2s。
模型分析
不允许线段交叉 → 连边的结构天然是"区间嵌套"的。即若 i<j<k<l,不可能同时有连边 (i,k) 和 (j,l)。
DP 对象是圆上区间
dp[l][r] = 区间 [l,r] 内的点形成一个连通块的方案数(且最外层连接点必须是 l 和 r)。f[l][r] = 区间内任意合法连边方案数。
转移:dp[l][r]=∑k=lr−1f[l][k]⋅dp[k][r](枚举 r 最左边的邻居)。f[l][r]=∑k=lr−1f[l][k]⋅dp[k][r]。O(n3)。
启示
空间的几何限制可以转化为 DP 对象的递归结构。这题的"树"不是输入的一棵树,而是从圆的几何性质中推导出来的。
题意:n 个珠子排成一行,颜色分别为 ci。每次操作:可以消除一个回文子段(消除后两侧拼接)。求消除整个序列的最少操作次数。
数据:n⩽500,ci⩽n。时限 2s。
Dl,r = 消除区间 [l,r] 的最少次数。若 cl=cr,可以"免费"把两边和内层一起消:Dl,r←Dl+1,r−1(注意当 l+1>r−1 时就是消掉两个相邻同色珠子)。否则枚举分割点:Dl,r=mink(Dl,k+Dk+1,r)。O(n3)。
题意:长度为 n 的木板初始无色。每次操作可以将一个连续区间涂成一种颜色(后涂覆盖先涂)。问最少几次操作能把木板变成目标颜色序列。
数据:n⩽50。时限 1s。
Dl,r = 涂好区间 [l,r] 的最少次数。若 al=ar,则 Dl,r=min(Dl+1,r,Dl,r−1)——要么涂左边时顺便带右边,反之亦然。否则枚举分割点。与 Zuma 对称。O(n3)。
题意:给定一个合法括号序列。给每个括号染三种颜色之一:红、蓝、无色。限制:
数据:序列长度(括号数)⩽700。时限 2s。
先预处理每个括号的配对位置 match[l]。dp[l][r][cl][cr] = 区间 [l,r] 两端颜色分别为 cl,cr∈{0,1,2} 的方案数。
若 r=match[l](l 和 r 配对),则内部区间 [l+1,r−1] 独立处理;否则拆为 [l,match[l]] 和 [match[l]+1,r] 两段拼接。O(n2)。
题意:字符串的折叠表示:一段连续重复的字符串可以写为 次数(子串),如 ABCABC → 2(ABC)。数字和括号也计入长度(如 2(ABC) 长度为 5)。求给定字符串的最短折叠表示长度。
数据:n⩽100。时限 1s。
Dl,r = 区间 [l,r] 的最短折叠长度。转移有两种:(1)枚举分割点拼接;(2)判断 [l,r] 能否由 [l,k] 重复若干次构成,若能则用 Dl,k+digit(cnt)+2 更新 Dl,r(cnt 为重复次数,括号贡献为 2)。O(n3)。
题意:数轴上有 n 个村庄,坐标 ai 递增。选 k 个村庄建邮局,每个村庄去最近的邮局。最小化所有村庄到其最近邮局的距离之和。
原题数据:n⩽3000,k⩽300。时限 2s。
训练版建议:若尚未学习四边形不等式或决策单调性,建议将数据改为 n⩽500, k⩽100,使用朴素 O(n2k) 区间划分 DP;原题数据适合放到 DP 优化专题。
dp[i][j] = 前 i 个村庄建 j 个邮局的最小距离和。一段村庄共享一个邮局时最优位置是中位数。
朴素转移枚举上一段起点,复杂度 O(n2k),训练版做到这里即可。原题数据需要四边形不等式优化决策单调性,复杂度 O(nk)。
题意:目标队形是一个序列 a1,…,an。n 个人的初始队伍为空,按某种顺序依次加入,每次新加入的人只能排在当前队伍的最左端或最右端。求有多少种加入顺序能形成目标序列 a。对 19650827 取模。
数据:n⩽1000。时限 1s。
从最后一步往前想:最后一个插入的人一定在队伍的两端。dp[l][r][0/1] = 区间 [l,r] 已形成且最后加入的是 l(左边,0)还是 r(右边,1)的方案数。根据与相邻元素的大小关系转移。O(n2)。
题意:初始空串。进行 n 步,每步:在当前序列的某个随机位置插入一对括号 ()(均匀随机选择插入点)。求 n 步后最终序列是合法括号序列的概率。对 998244353 取模。
数据:n⩽500。时限 2s。
模型分析
有时间维度(每组括号"加入"的时间不同),直接按时间 DP 行不通:正着信息量太大,倒着会拆散同组括号。
找静态锚点:第一组括号
考虑第一组加入的括号(最早插入的)。在最终序列中,它们把序列切成三部分:左边的 ( 前面、两个括号之间、右边的 ) 后面。同组括号必在同一部分。
转化为求方案数:dp[i][j] = 长度为 i、当前前缀多余的 ( 数为 j 的方案数。最后用概率公式还原。O(n3),卷积优化可至 O(n2)。
启示
多维限制下,找一个不变的"锚点"作为递归拆分点。
题意:给定布尔函数 f(x,y,z) 的真值表(长度为 8 的 01 串,表示对 8 种输入 000~111 的输出)。求表示该函数的最短逻辑表达式。允许 AND(&)、OR(|)、NOT(!)、括号。优先级:括号 > NOT > AND > OR。有 t 组询问。
数据:t⩽256(即最多 256 个不同的真值表)。时限 3s。
按表达式层级(优先级)BFS 递推。先算所有单字符表达式(x, y, z),然后逐层通过 NOT、AND、OR 组合出更复杂的表达式。对每个真值表记录最短表达式。O(t×8×28)。
题意:n 个节点的有根树(根为 1),每个节点上有一个括号 ( 或 )。设 ki 为从根到节点 i 的路径形成的字符串中,有多少个连续子串是合法括号序列。求 ⨁i=1n(i⋅ki)。
数据:n⩽5×105。时限 1s,内存 256MB。
DFS 时维护一个栈(记录左括号的下标)。当遇到右括号时,若栈不空则弹出一个左括号,设它的下标为 p,则 dp[u]=dp[fa[p]]+1(以 u 结尾的合法子串数)。回溯时恢复栈。ku=kfa+dp[u]。O(n)。
题意:n×n 棋盘上放 k 个国王(攻击范围:周围 8 格)。国王之间不能互相攻击。求方案数。
数据:n⩽9,k⩽n2。时限 1s。
状压 DP。预处理每行合法状态(没有相邻 1)及每种状态放置的国王数。再预处理两行间的兼容性。dp[i][mask][cnt] 逐行递推。合法状态远少于 2n。
题意:n×m 网格,部分格子是山(P,不可放)。炮兵攻击范围:上下左右各 2 格(共 4 方向 × 2 = 可达 12 格,但对称)。求最多能放多少个炮兵。
数据:n⩽100,m⩽10。时限 1s。
攻击范围跨两行 → 状态需记录前两行的部署情况。dp[i][S1][S2] = 第 i 行状态为 S1、第 i−1 行状态为 S2 时的最大炮兵数。滚动数组优化空间。O(n×3m) 或更优。
题意:n 个珠子排成一列,颜色 ci⩽20。每次操作交换相邻两颗珠子。求使所有同色珠子形成连续一段的最少交换次数。
数据:n⩽4×105,ci⩽20。时限 4s。
模型简化(核心!)
最终每种颜色的珠子是连续的一段 → 只需决定 20 种颜色的排列顺序。最少相邻交换次数 = 逆序对数。
换 DP 对象:从珠子到颜色
预处理 w[i][j] = 将颜色 i 的所有珠子全部排在颜色 j 的所有珠子前面时,颜色 i 和 j 之间的逆序对数(O(202n))。
dp[mask] = 已决定排列顺序的颜色集合为 mask(二进制),这些颜色排在其它颜色前面的最小逆序总和。转移:枚举下一个要排的颜色 k∈/mask,增加 ∑j∈maskw[j][k]。O(20×220)。
启示
n 很大但颜色很少 → DP 对象从"珠子"换成"颜色"。Step 2 的极致体现。
题意:n 个点 m 条边的无向图。求简单环的个数(不重复经过节点的环,长度 ⩾3)。边集相同的环视为同一个。
数据:n⩽19,m 无特殊限制。时限 2s。
dp[mask][last] = 从 mask 中编号最小的点出发、以 last 结尾、经过点集为 mask 的简单路径数。枚举下一个邻居扩展路径。当 last 能连回起点且路径长度 ⩾3 时计入答案。最终答案除以 2(每条环正反各算一次)。O(2nn2)。
题意:n 个数 ai。对每个 ai,找一个 j=i 使得 ai&aj=0(即两个数的二进制表示无任何一位同时为 1)。若不存在输出 −1。
数据:n⩽106,ai<222。时限 4s。
SOS DP(子集 DP)。dp[mask] = 任意一个"是 mask 的子集"的 aj 的值(若存在)。从低维向高维递推:dp[mask]←dp[mask⊕2i](若 dp[mask] 为空且 dp[mask⊕2i] 有值)。
对每个 ai,答案为 dp[∼ai&((1≪22)−1)]。O(22×222)。
题意:n 个城市(编号 0∼n−1),有 m 条无向边,每个城市有人口 wi。将城市划分成若干州。一个州合法当且仅当:(1)州内城市构成连通块;(2)州内不存在欧拉回路。每个州的满意度为 (S/T)p,其中 S 是州内人口和,T 是总人口和。求所有合法划分方案的总满意度,对 998244353 取模。
数据:n⩽21,m⩽(2n),p∈{0,1,2}。时限 10s。
子集卷积 + FWT。先判断每个子集 mask 是否是合法州:需检查连通性(只需两条及以上不交路径或度数判定)和欧拉回路存在性(每个点度数均为偶数)。
FM=∑S⊂MFM⊕S⋅WS。按 popcount 分层,每层做 FWT(或 FMT),然后逐位做多项式乘法。O(n22n)。
题意:n 个点 m 条边的 DAG(有向无环图),每条边有整数权值 w(可正可负)。求从 1 到 n 的最长路径长度。若 1 不能到达 n,输出 −1。
数据:n⩽1500,m⩽5×104,−105⩽w⩽105。时限 1s。
拓扑序 DP。初始化 dp[1]=0,其余为 −∞。按拓扑序松弛:dp[v]=max(dp[v],dp[u]+w)。最后若 dp[n]=−∞ 则输出 −1。O(n+m)。
题意:n 个点 m 条边的有向图。每个节点上有一个小写字母。一条路径的"价值"定义为该路径上出现次数最多的字母的出现次数。求最大路径价值。若图中有环(价值可无限大)则输出 −1。
数据:n,m⩽3×105。时限 3s。
先拓扑排序判环。dp[u][c] = 以 u 结尾的路径中,字母 c 的最大出现次数。令 chv 表示点 v 上的字母,按拓扑序传递并更新 dp[v][c]=max(dp[v][c],dp[u][c]+[chv=c])。O(26(n+m))。
题意:n 个点 m 条边的 DAG。对每个点 i,求以 i 为终点的最长路径能经过多少个城市(包含起点和终点)。
数据:n⩽105,m⩽2×105。时限 1s。
拓扑序 DP。dp[v]=max(u,v)∈E(dp[u]+1),初始所有 dp[i]=1。O(n+m)。
题意:n 个点 m 条边的有向图,每个点有权值 wi。找一条路径(允许重复经过节点,但每个点的权值只算一次),求最大权值和。
数据:n⩽104,m⩽105,wi⩽1000。时限 1s。
Tarjan 缩点(SCC)→ 得到 DAG,新点的权值为 SCC 内所有点权值之和。然后在新 DAG 上拓扑序 DP 最长路。O(n+m)。
题意:给定两个正整数 a,b。求 [a,b] 中每个数码 0~9 各出现了多少次。
数据:1⩽a⩽b⩽1012。时限 1s。
数位 DP 基础模板。从高位到低位逐位构造。状态:当前位 pos、是否贴上限(lim)、是否有前导零(lead)。记忆化搜索,O(10×log10b×2×2) 每种数码一次。
题意:不含前导零且任意相邻两个数码之差的绝对值 ⩾2 的正整数称为 windy 数。求 [a,b] 中有多少个 windy 数。
数据:1⩽a⩽b⩽2×109。时限 1s。
比模板多一维:dp[pos][pre] = 当前位为 pos、上一位填了 pre 时的方案数(不含贴上限和前导零的情况)。转移时检查 ∣cur−pre∣⩾2。
题意:定义 d-magic 数:在偶数位置上的数码必须是 d,在奇数位置上的数码不能是 d(位置从 1 开始,1 为最高位),且无前导零,且整个数字能被 m 整除。求区间 [L,R] 中 d-magic 数的个数,对 109+7 取模。
数据:∣L∣,∣R∣⩽2000(即数字最多 2000 位),m⩽2000,0⩽d⩽9。时限 2s。
多一维"当前数字 modm 的余数"。状态:dp[pos][rem][lim][lead]。根据当前位置的奇偶性决定合法数码集合。O(∣R∣×m×10)。
题意:求有多少个 n 位十进制正整数(无前导零),其十进制表示中不包含子串 S(S 为给定的非空数字串)。对 k 取模(k 不一定是质数)。
数据:n⩽109,∣S∣=m⩽20,k⩽1000。时限 1s。
KMP 自动机
建 S 的 KMP 自动机(即 next 数组 + 转移表)。状态为当前在自动机的哪个节点(即已匹配 S 的前几个字符)。
矩阵快速幂优化
由于 n⩽109,不能逐位递推。注意到转移是线性且状态数少(m),写出转移矩阵 A(Ai,j = 从状态 i 经过填一个数码后到达状态 j 的方案数),用矩阵快速幂 O(m3logn)。
注意:状态 m(匹配完整个 S)是非法状态,转移矩阵中要排除。
启示
发现转移是线性的 → 矩阵快速幂将 O(n) 降到 O(logn)。Step 4 最经典案例。
题意:n 种花共摆 m 盆,第 i 种最多 ai 盆。同种花必须放一起,且按种类编号从小到大排列。求方案数,对 106+7 取模。
数据:n,m⩽100,0⩽ai⩽100。时限 1s。
多重背包求方案数。dp[i][j] = 前 i 种花放 j 盆的方案数。dp[i][j]=∑k=0min(ai,j)dp[i−1][j−k]。滚动数组 + 前缀和优化:dp[j]=sum[j]−sum[j−ai−1]。O(nm)。
题意:n 件商品,第 i 件需要 ti 秒扫描,价格 ci。收银台在扫描某件商品时,在接下来 ti 秒内,其他商品若无防盗标签,可以被偷走。你需要决定为哪些商品购买防盗标签(买了标签的商品不会被偷,价格为 ci),使得所有 n 件商品都安全。求最小花费。
数据:n⩽2000,ti⩽2000,ci⩽109。时限 1s。
转化:扫描第 i 件商品相当于"覆盖"了 ti+1 个位置(它自己 + 它保护的后 ti 件)。dp[j] = 获得至少 j 单位"覆盖"的最小花费。做"至少"背包:dp[j]=min(dp[j],dp[j−ti−1]+ci),j 从大到小,且可超出 n。O(n2)。
题意:有 n 个非递减数组(第 i 个长度为 li)。你需要选恰好 k 个元素,限制:对每个数组只能选它的一个前缀(可以为空)。求最大总和。
数据:n,k⩽3000,∑li⩽106。时限 2s。
模型性质挖掘
关键性质:至多一个数组会"部分选"(选了前缀但没选完)。因为所有数组都是非递减的,拿后面的数只会更大,如果某个数组选了但没选完,不如再从那拿一个替代另一个数组的。
分治处理
分治确定哪个数组"部分选"。递归到区间 [L,R] 时,强制这个范围内的某个数组可能是部分选的。通过分治过程中的背包合并,保证只有当前区间外的数组被"全部选完或全部不选"。O(nklogn)。
题意:给定一个多重集 a1,…,an(可能有重复)。你可以把 a 划分成若干组(组间无序)。问能生成多少种不同的多重集,其中"不同"是指"每组大小"的分布(即 {size1,size2,…} 组成的多重集)不同。
数据:n⩽2000。时限 2s。
逐种大小 DP(不是逐个元素!)。cnt[x] = 数值 x 的出现次数。prek=∑min(cnt[i],k) = 前 k 大的组最多能容纳多少个元素。dp[i][j] = 确定了最大的 i 种 size、总共放了 j 个元素的方案数。状态数 O(n2logn)。转移 O(1)(新加一种 size 或全体 +1)。对 998244353 取模。
题意:给定 n 个正整数 ai 和两个整数 L,R。求有多少个 b∈[L,R] 可以表示为 ∑ki⋅ai(ki 为任意非负整数,即每个物品无限取用)。
数据:n⩽12,ai⩽5×105,L,R⩽1012。时限 1s。
模型分析
L,R⩽1012 太大,逐容量 DP 不可行。需要换一个视角。
最激进的重定义:从容量到余数
取最小的 a(设为 a0),按 moda0 的余数分类。dis[r] = 模 a0 余 r 的最小可达值(即最小的能表示为 ∑kiai 且 ≡r(moda0) 的数)。
建图:对每个余数 r 和每个 ai,连边 r→(r+ai)moda0,权 ai。跑 Dijkstra 最短路求 dis。
最后统计:对每个余数 r,区间 [L,R] 中 ⩾dis[r] 且 ≡r(moda0) 的数的个数。O(na0loga0)。
启示
DP 对象从"容量"变成"余数"——Step 2 中最激进的重定义。同余最短路。