1 条题解
-
0
题目背景与分析
题目要求我们在一个给定的 Trie 树结构(给出了父子关系和部分边上的字符)中,填补所有的
?,使得从根节点到每一个叶子节点所形成的路径字符串,按逗号,分割后,都能构成一个严格递增且无前导零的正整数序列。在此基础上,要求最终形成的字符串字典序最小。这是一个典型的贪心 + 动态规划 (DP) / 校验的问题。
核心思路
为了使最终字符串字典序最小,我们应当采用贪心策略: 从树的根节点开始(或者按照输入的字符串顺序,即边的编号顺序),对于每一个是
?的位置,从小到大尝试填入字符。字符的字典序顺序为,<0<1< ... <9。当我们尝试填入一个字符时,必须判断这个选择是否合法。所谓“合法”,是指在填入这个字符后,是否存在一种后续的填法,使得整棵子树都能满足“路径构成的数列严格递增”这一条件。
因此,问题的关键在于如何高效地进行可行性校验。
算法详解
1. 状态定义与预处理 (DFS / DP)
为了支持贪心过程中的可行性判断,我们需要先进行一次自底向上的 DP(在代码中体现为
dfs函数)。我们需要维护子树对当前节点上层数字的“约束”。对于 Trie 树上的一个节点 ,如果它的祖先中最近的一个逗号(分隔符)确定了上一段数字结束,那么从那个逗号到 的路径形成了一个正在构建的数字的前缀 。 为了让 的所有子树(叶子)都能合法,我们需要知道:
- 子树中对当前正在构建的数字有什么要求?
- 显然,子树中的路径可能会延续这个数字,直到遇到下一个逗号。
- 设从 出发延伸到子树里的下一个逗号前形成的数字后缀为 ,那么 构成的数字必须大于该路径上上一个逗号前的数字 。
- 对于 的所有分支,我们需要找到限制最紧(即要求当前数字尽可能大,或者在长度相同时数值最大)的那个约束。
代码中的
dp数组、dd、g等变量正是为了维护这些复杂的字符串比较和边界信息。g[...][len]: 维护子树中要求的最长/最大的数字后缀信息。merge函数:这是核心逻辑。当一个节点有多个子节点时,要满足所有子节点的合法性,就必须满足其中“要求最高”的那个子节点的限制。比如一个子节点要求当前数字至少是 12345,另一个要求至少是 123,那么当前数字必须能凑出 12345。
2. 贪心构造 (Solve)
solve(id)函数实现了自顶向下的贪心填数过程。-
遍历字符集:对于当前节点
id,如果它是?,则按顺序尝试字符char c \in {',', '0', '1', ..., '9'}。如果它是固定字符,则只检查该字符。 -
前导零检查:如果上一位是逗号(或者是根节点),当前位不能填
0(除非该数字就是0且后面紧跟逗号,但题目要求正整数,通常不含前导零的定义排除了单独的 0 或者 0 开头)。 -
合法性检查 (Check):
- 如果是逗号
,:意味着当前数字结束。我们需要检查刚刚生成的这个数字(从上一个逗号到当前位置)是否严格大于路径上再前一个数字。这需要回溯祖先节点拼接字符串进行比较(代码中st数组用于提取路径字符串)。同时,还需要确保子树中所有节点都能接受在这个位置结束数字(即子树的 DP 状态允许在这里截断)。 - 如果是数字
0-9:意味着当前数字还在延伸。我们需要检查加上这个数字后,是否还能满足子树中记录的 DP 约束。例如,如果子树要求这一位必须大于等于3,那么填2就是不合法的。代码利用预处理的dp信息和merge的逻辑来快速判断。
- 如果是逗号
-
递归:一旦找到一个合法的字符,就将其填入,并递归处理子节点。如果所有字符都试过仍不合法,则回溯(对于本题,因为要求字典序最小,一旦确定一位通常不需要回溯修改已确定的高位,除非该位导致无解,输出 failed)。
代码结构解析
-
merge(a, b, c, d, e, f):- 这是最复杂的辅助函数。它的作用是合并两个子树的约束信息。
- 它比较两段字符串(代表数字的约束),保留限制更严格的那一个(通常是长度更长,或者长度相同字典序更大的)。
len[0],len[1],st[0],st[1]用于提取和比较这些约束字符串。
-
dfs(id):- 自底向上的预处理。
- 首先递归处理所有子节点
adj[id]。 - 然后通过
merge操作,将所有子节点的约束合并到当前节点id上,存储在dp及相关辅助数组中。 - 这步确立了“为了让子树有解,当前节点及上方的路径必须满足的最低条件”。
-
solve(id):- 自顶向下的填空。
if (str[id] == ','): 处理逗号的情况,主要涉及与上一个数字的大小比较(提取pa[id]往上的路径直到上一个逗号)。else(数字): 处理数字的情况,主要检查是否违反了dfs计算出的下界约束。- 如果找到合法字符,直接修改
str[id]并继续处理子节点。
-
main():- 处理多组数据。
- 构建邻接表
adj。 - 调用
dfs(0)预处理。 - 调用
solve(0)构造答案。 - 如果
solve返回 false,输出failed;否则输出结果字符串。
复杂度
- 数据范围:,数据组数 。
- 虽然 较小,但字符串操作较多。
dfs过程中涉及字符串比较和合并,单次合并复杂度与深度有关,最坏 。节点数 ,总预处理复杂度可达 或 。solve过程中,每个节点尝试常数个字符(11个),每次检查涉及回溯祖先和比较,复杂度 。- 总复杂度大致在 级别,对于 完全可以接受(,运算量较小)。
总结
这份代码通过 “自底向上维护最紧约束” 和 “自顶向下贪心匹配” 解决了问题。
- DP (DFS) 阶段:算出每个节点如果是数字的一部分,它后面跟着的后缀至少要是多少,才能满足子树里所有递增序列的要求。
- 贪心 (Solve) 阶段:尝试填入最小字符,利用 DP 算出的“后缀下界”和当前已填的前缀,判断是否能构成合法序列(当前数字 > 上一个数字,且满足子树约束)。
- 子树中对当前正在构建的数字有什么要求?
- 1
信息
- ID
- 6099
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者