你是校史馆的信息化志愿者,刚接手一个数字化项目,馆长给你派了两件看起来很不一样的活儿。
任务一(档案校对):馆里有两份校友手写的回忆稿,一份是 A 稿,一份是 B 稿。馆长想知道:「把 A 稿改成 B 稿,最少要动几个字?」允许的动作只有三种:插入一个字、删除一个字、替换一个字。A 稿可能有两千字,逐字比对显然不现实。
任务二(年会邀请名单):校友会要在年会上邀请一部分校友。但校友之间存在明显的师承链——老教授带出的学生、学生又带出学生,整条师承关系正好是一棵树。出于礼节,馆长定了条规矩:同一条师承链上「直接相邻」的两个人,不能同时邀请(否则师弟见了师兄会尴尬)。每个人有一个「出席贡献值」,请在规矩之内让总贡献值最大。
任务三(预算分配):如果年会预算有限,只能邀请 恰好 m 位校友,且校友的师承关系要求「要请学生,必须先请他的导师」,那么该请哪 m 位?
三个任务,对应动态规划的两大流派。
• 任务一的两个「稿子」天然构成一张二维表格:行是 A 稿的前 i 个字,列是 B 稿的前 j 个字,格子里的数就是「把前 i 个字变成前 j 个字的最少操作数」——这就是多维动态规划;
• 任务二、任务三的对象是一棵树:每个人的「要不要邀请」以及「请几位」,都只取决于他的子树内部——把状态挂到树上、后序遍历自底向上合并,就是树型动态规划。
多维 DP 解决「两个序列之间」的问题,树型 DP 解决「一棵树上」的问题——形式不同,套路完全一致。
💡 故事背后的算法思想
共同点:两类问题都在做同一件事——把一个大问题拆成若干个「规模更小的同类子问题」,用一张表把子问题的答案存起来,再按既定顺序拼出大问题的答案。
多维 DP 的「表」是二维(甚至三维)数组,任一格子只依赖它左边、上边、左上的格子,所以按行(或按列)从前往后填即可;树型 DP 的「表」是每个点上的若干状态,任一状态只依赖它儿子的状态,所以用后序遍历自底向上算即可。
工程意义:很多同学做 DP 卡住,不是因为「不会写循环」,而是因为「不知道状态该怎么定」。本讲的核心训练目标就是:看到题目能迅速说出「状态是什么、从哪来、边界在哪」这三件事。