DP 学习笔记

二维 / 多维 DP

知识点

  • 状态定义:使用两个或更多维度表示问题的不同阶段或约束。例如 dp[i][j] 表示处理了前 i 个元素,当前容量 / 位置 / 剩余次数为 j 时的最优值或方案数。
  • 典型场景:网格路径(数字三角形)、背包问题、双线程走方格等。
  • 转移方程:根据最后一步的可能决策,从之前的状态递推而来。
  • 初始化:边界值(dp[0][j]dp[i][0])需根据问题设定,通常为 0、∞ 或特殊值。
  • 优化技巧(了解):
    • 滚动数组:当转移只依赖上一行时,可将第一维压缩成 2 或直接倒序枚举(0/1 背包)。
    • 降维:若状态可合并(如方格取数中两路径步数同步),可将四维降为三维。

常用技巧

  • 数字三角形:从上往下(dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]) + a[i][j])或从下往上(避免边界判断)。
  • 背包问题:求方案数时 dp[j] = sum(dp[j], dp[j-w[i]]),注意初始化 dp[0]=1
  • 多条路径问题:利用步数相同性质,设 dp[k][i1][i2] 表示走了 k 步,第一条路径到达第 i1 行,第二条到达 i2 行,列可由 k-i 算出。

题目题解

P1164 小 A 点菜

题意:有 n 道菜,每道菜价格 a[i],口袋有 m 元,求正好花完 m 元的点菜方案数。每道菜只能点一次。

思路:0/1 背包求方案数。

  • 状态定义:dp[j] 表示凑出 j 元的方法数。
  • 初始:dp[0] = 1(不花钱有一种方案)。
  • 转移:对每道菜 i,倒序枚举 jma[i],执行 dp[j] += dp[j - a[i]]
  • 答案:dp[m]

二维理解:dp[i][j] 表示前 i 道菜凑出 j 元的方案,有 dp[i][j] = dp[i-1][j] + (j>=a[i]? dp[i-1][j-a[i]] : 0)。一维为滚动数组压缩。


P1216 [IOI1994 / USACO1.5] 数字三角形 Number Triangles

题意:给定数字三角形,从顶部出发,每次向下或右下方移动,求路径上数字和的最大值。

思路:经典二维 DP。

  • 状态定义:dp[i][j] 表示从顶部走到第 i 行第 j 列的最大和。
  • 转移:dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]) + a[i][j](注意边界,j=1j=i 时只有一个来源)。
  • 答案:max(dp[n][j])
  • 优化:可自底向上递推,省去求最大值步骤:dp[i][j] = a[i][j] + max(dp[i+1][j], dp[i+1][j+1]),答案即 dp[1][1]

P1004 [NOIP2000 提高组] 方格取数

题意N×N 网格,每个格子有一个数字。从 (1,1) 走到 (N,N) 两次(路径可重叠),每次经过的格子数字会被取走(第二次经过时数字已为 0),求两次路径取数总和的最大值。

思路:等价于两人同时从 (1,1) 走到 (N,N),且每一步同时移动。

  • 状态定义:设走了 k 步,第一条路径当前在 (i1, j1),第二条在 (i2, j2),有 i1+j1 = i2+j2 = k+2(假设起点步数为 0)。可压缩为 dp[k][i1][i2]
  • 转移:每人可向下或向右,共 4 种组合。当前步的取值为 val = a[i1][j1] + (i1==i2 && j1==j2 ? 0 : a[i2][j2])
  • 答案:dp[2*N-2][N][N]
  • 时间复杂度 O(N^3)

二、区间 DP

知识点

  • 状态定义dp[l][r] 表示区间 [l, r] 上的最优值(如合并最小代价、最大得分等)。
  • 转移方式:通常枚举区间长度 len,再枚举左端点 l,计算右端点 r = l+len-1。然后枚举分割点 kl ≤ k < r),合并左右子区间。
  • 经典递推dp[l][r] = min(dp[l][k] + dp[k+1][r]) + cost(l,r)
  • 初始化dp[i][i] 通常为 0(单点无需合并)。
  • 环形处理:将序列复制一倍接到末尾,破环成链,然后对每个长度为 n 的区间取最值。

常用技巧

  • 石子合并:合并代价常为区间内石子总数,可用前缀和 O(1) 计算。
  • 记录最优分割点(Knuth 优化)可加速(了解)。
  • 对于“消除 / 合并类”游戏(如 248 G),注意只有当相邻数字相等时才能合并,且合并后数字 +1。

题目题解

P1775 石子合并(弱化版)

题意n 堆石子排成一行,每次合并相邻两堆,代价为两堆石子数之和,求合并为一堆的最小总代价。

思路:基础区间 DP。

  • 状态:dp[i][j] 表示合并区间 [i, j] 的最小代价。
  • 转移:dp[i][j] = min_{i≤k<j}(dp[i][k] + dp[k+1][j]) + sum(i,j),其中 sum(i,j) 为区间石子总数,可用前缀和快速得到。
  • 初始化:dp[i][i]=0,其余为无穷大。
  • 答案:dp[1][n]
  • 复杂度 O(n^3),数据弱化时可通过。

P1880 [NOI1995] 石子合并

题意:与 P1775 类似,但石子摆成环形。

思路:破环成链。

  • 将石子数组复制一份接在后面,得到长度 2n 的数组。
  • 对每个长度为 n 的区间 [i, i+n-1] 执行与弱化版相同的区间 DP,求出该区间的合并最小代价。
  • 答案:min_{1≤i≤n} dp[i][i+n-1]
  • 同样可求最大值(题目有时要求两者),只需将 min 改为 max,并注意初始化边界。

P3146 [USACO16OPEN] 248 G

题意:给定一行 n 个正整数(1~40),每次可选相邻两个相等数字 x 合并成 x+1,求最终能得到的最大数字。

思路:区间 DP 变种。

  • 状态定义:dp[i][j] 表示区间 [i, j] 能否完全合并成一个数字,若能则记录该数字;否则为 0。
  • 转移:dp[i][j] 可由两个子区间合并得到,即存在分割点 k 使得 dp[i][k] == dp[k+1][j] != 0,此时 dp[i][j] = dp[i][k] + 1
  • 初始化:dp[i][i] = a[i]
  • 答案:所有 dp[i][j] 中的最大值。
  • 注意:区间长度从小到大枚举,合并时要求左右子区间都完全合并成单一数字且相等。

三、树形 DP

知识点

  • 状态定义:以树节点为下标,通常设 dp[u][state] 表示以 u 为根的子树在某种状态(如选或不选 u)下的最优值。
  • 转移方式:从叶子向上(后序 DFS),通过合并子节点的信息得到父节点。
  • 常见模型
    • 树的最大独立集(没有上司的舞会)
    • 树的最小点覆盖
    • 树形背包(在树上分组背包)
    • 树的直径、最大子树和等

常用技巧

  • 建图:无向边,用邻接表存储。
  • DFS 时避免重复访问父节点,传入 parent 参数。
  • 对于“选与不选”的问题,设计 dp[u][0]dp[u][1]
  • 最大子树和:每个节点可“剪枝”掉某些子节点(即不选该子树的贡献),等价于选取一个连通子图,使得权值和最大。

题目题解

P1352 没有上司的舞会

题意:树形结构,每个节点有快乐指数。若选了某个节点,则不能选它的父节点和子节点(即直接相邻的点不能同时选)。求最大快乐指数和。

思路:经典树形 DP。

  • 状态:dp[u][0] 表示以 u 为根的子树中,不选 u 的最大快乐值;dp[u][1] 表示选 u 的最大快乐值。
  • 转移:
    • dp[u][1] = a[u] + sum( dp[v][0] ) (选 u 则子节点都不能选)
    • dp[u][0] = sum( max(dp[v][0], dp[v][1]) )(不选 u,子节点可选可不选)
  • 答案:max(dp[root][0], dp[root][1])
  • 任选一个根(如 1)进行 DFS。

P1122 最大子树和

题意:树上每个节点有整数权值(可正可负)。可以删除任意节点(连同其与父节点的边),但不能删除所有节点(至少保留一个节点)。求剩下的连通块(必须包含根?题目实际是求一棵树的最大权值连通子图,不一定包含原根,但通常以任意节点为根,取最大值)的最大权值和。

思路:树形 DP 求最大子段和。

  • 状态定义:dp[u] 表示以 u 为根的子树中,包含 u 的最大权值和(即选 u 及其部分后代形成的连通块)。
  • 转移:dp[u] = a[u] + sum_{v∈son(u)} max(dp[v], 0)。也就是说,若子节点子树的贡献为正,则加入;否则舍弃(剪枝)。
  • 答案:所有节点 dp[u] 中的最大值(因为连通块不一定从原根出发,可以以任意节点为根)。
  • 注意:初始化 dp[u] = a[u],最后求 max(dp[u])

四、数位 DP

知识点

  • 作用:统计区间 [L, R] 内满足某种性质的数字个数或数字和。
  • 核心思想:按位处理,利用记忆化搜索,状态记录当前处理到第几位、是否受上界限制、是否有前导零等。
  • 模板dfs(pos, limit, lead, ...),返回从当前位开始构造的所有合法数的贡献。
  • 常用优化:记忆化 dp[pos][state] 但只在不限位上界(!limit)时使用,因为 limit 变化少。

常用技巧

  • [L, R] 转化为 [0, R] - [0, L-1]
  • 处理前导零对计数的影响(例如统计数字 0 的出现次数时需单独处理)。
  • 对于数字和类问题,记录当前已积累的数字和。
  • 若统计每个数码的出现次数,可分别对 0~9 做一次数位 DP,或者在一个 DFS 中同时统计。

题目题解

P2602 [ZJOI2010] 数字计数

题意:给定两个正整数 a, b,求 [a, b] 中所有数字的十进制表示中,每个数码(0~9)各出现了多少次。

思路:分别统计 0~9 每个数码的出现次数。

  • 设计 dfs(pos, sum, limit, lead) 返回从当前位开始构造的所有数字中,目标数码 d 出现的总次数。
    • pos:当前处理到的位数(从高位到低位)。
    • sum:已经统计到的目标数码出现次数。
    • limit:是否受到上界限制。
    • lead:是否有前导零。
  • pos 到达结束位时,返回 sum
  • 对每一位枚举 0~9(根据 limit 限制),递归累加结果。
  • 注意:当 d=0 时,前导零不计入统计,需要特殊处理。
  • 对每个数码跑一次 DFS,记忆化状态 dp[pos][sum][lead]limit 变化时不能用)。

P4999 烦人的数学作业

题意:给定 L, R,求 [L, R] 中所有数字的各位数字之和的总和。例如 [1,12] 的和为 1+2+3+...+9+1+0+1+1+1+2 = ?

思路:统计每个数字的各位和,然后累加。

  • 数位 DP 状态:dfs(pos, sum, limit),返回从当前位开始构造的所有数字的 各位数字之和的总和
    • pos:当前位数。
    • sum:已经累计的各位数字之和(当前已确定的高位贡献)。
    • limit:上界限制。
  • 记忆化:dp[pos][sum] 只在 !limit 时记录。
  • 转移:枚举当前位数字 i,递归 dfs(pos-1, sum+i, limit && i==up),然后将子结果累加到总和中。
  • 边界:pos==0 时返回 sum
  • 答案:用 solve(R) - solve(L-1)

posted @ 2026-06-14 11:44  spring_sun  阅读(36)  评论(0)    收藏  举报