字符串 题解

字符串 题解

题意

设删去第 \(k\) 条记录,则要为剩下的 \(n-1\) 条记录分配串 \(s_1,s_2,\dots,s_{n-1}\),满足:

  1. \(|s_i| = l_i\)(长度固定,跳过被删的那条);
  2. \(s_1 < s_2 < \cdots < s_{n-1}\)(按原顺序,字典序严格递增,规定 \(0<1\));
  3. 任意两个串互不为前缀。

每个删除位置独立判断,不同位置可以用不同的分配方案。

直接对着"字符串"思考很困难:串的取值有指数多种。突破口是把三个约束全部量化关系,让"串"消失。


第一步转化:串 \(\to\) 二叉树节点

"任意一个串都不是另一个串的前缀"------这正是前缀编码的限制。把所有 01 串放进一棵满二叉树(Trie):

  • 从根出发,读到 0 走左儿子,读到 1 走右儿子;
  • 每个串对应树上唯一一个节点 ,串长就是节点深度。

于是三个约束变成:

字符串世界的约束 二叉树的含义
\(s\) 是 \(t\) 的前缀 \(s\) 对应的节点是 \(t\) 对应节点的祖先
两两无前缀关系 选中的节点互不为祖先
字典序递增 选中的节点从左到右依次排列

题目变成:给定各节点的深度序列,删除一个后,能否按原顺序从左到右选出互不为祖先的这些节点?

第二步转化:节点 \(\to\) 叶子区间

节点之间的"祖先 / 左右"关系还是不够具体。把树补全到深度 60 (因为 \(l_i\le 60\)),最底层共有

\(U = 2^{60}\)

片叶子,从左到右编号 \(0\sim U-1\)(叶子从左到右恰好是二进制值 \(0,1,2,\dots\) 递增,与 \(0<1\) 的规定一致)。

每个节点对应一段连续的叶子区间。 以深度 2 为例,底层叶子依次是 00,01,10,11:

串 它下面的叶子 对应区间
0 00, 01 \([0,2)\)
01 01 \([1,2)\)
1 10, 11 \([2,4)\)

一般的:长为 \(l\)、二进制值为 \(b\) 的串,其子树的叶子恰是所有以它为前缀的 60 位 01 串,即

\(\left[\,b\cdot 2^{60-l},\ (b+1)\cdot 2^{60-l}\,\right).\)

记 \(w_i = 2^{60-l_i}\)(块宽 ),那么每条记录对应一段长度为 \(w_i\)、起点是 \(w_i\) 的整数倍(对齐) 的区间。这一步"对齐"就是节点区间把 \([0,U)\) 均匀切分的体现。

三个关系全部量化:

二叉树 叶子区间
祖先 - 后代 区间包含
互不为祖先 区间互不相交
从左到右 区间按此顺序排列

问题重述 :把 \(n\) 条记录看成 \(n\) 个块,第 \(i\) 块长 \(w_i = 2^{60-l_i}\)。删掉一个块后,能否把其余块按原顺序 、互不相交 地放进 \([0,U)\),且每块起点是块宽的倍数?

(可以紧贴:前一块终点 = 后一块起点是允许的,只要不共享叶子------对应"同一片叶子不能同时属于两个被选节点"。)

为什么"总长够"不等于"放得下"

用深度 2(\(U=4\))演示:长度 \((2,1,2)\) 对应块宽 \((1,2,1)\),总宽恰好 4。

  • 第一块最早放在 \([0,1)\);
  • 第二块必须对齐 2 的倍数 ,最早只能放 \([2,4)\);
  • 第三块已无处可放。

顺序和对齐缺一不可,这正是本题不能只看 \(\sum w_i \le U\) 的原因。


暴力:\(O(n^2)\) 贪心

枚举删除位置 \(k\),对剩下的块从左到右 依次放置,每块放在最靠左的合法位置:

设前面所有块已在 \([0,u)\) 内放好(\(u\) 是当前右端点),当前块宽 \(w\)。合法起点必须是对齐 \(w\) 且 \(\ge u\) 的最小值,即 \(w\left\lceil \dfrac{u}{w}\right\rceil\),故右端点更新为

\(u \leftarrow w\left(\left\lceil \frac{u}{w}\right\rceil + 1\right),\qquad u \text{ 初始为 } 0.\)

若某块放不下(\(u > U\))则该 \(k\) 失败;全部放下则成功。 \(O(n^2)\)

贪心为什么不劣? (交换论证)前面结束得越早,后面所有块的可用空间是 \([u,U)\) 的子集式放宽------任何"放得下"的方案中,第 \(i\) 块的起点 \(s\) 都满足 \(s \ge\) 贪心的右端点且对齐,所以贪心起点 \(\le s\),贪心永远不会比某个可行方案更早失败。


正解:前后缀预处理 + \(O(1)\) 判定

\(O(n^2)\) 的浪费在于:删不同位置时,相同的左侧前缀被反复重放。左右两段其实与"删谁"无关,可以各预处理一次。

前缀:最靠左贪心 \(F\)

设 \(F_i\) = 只放前 \(i\) 块 (原序列的第 \(1\sim i\) 块)时,最靠左放法的右端点;\(F_0 = 0\)。第 \(i\) 块宽 \(w_i\),合法起点 \(\ge F_{i-1}\) 且对齐,最小起点为 \(w_i\left\lceil F_{i-1}/w_i \right\rceil\),因此

\(\boxed{\,F_i = w_i\left(\left\lceil \frac{F_{i-1}}{w_i}\right\rceil + 1\right)\,}\)

一遍从左到右 \(O(n)\) 求出。

后缀:最靠右贪心 \(G\)

对称地,从右往左,把每块放在最靠右 的合法位置。设 \(G_i\) = 只放第 \(i\sim n\) 块 时最靠右放法的左端点;\(G_{n+1} = U\)(右边没有约束)。第 \(i\) 块的右端点须 \(\le G_{i+1}\) 且对齐,最大右端点为 \(w_i\left\lfloor G_{i+1}/w_i\right\rfloor\),因此

\(\boxed{\,G_i = w_i\left(\left\lfloor \frac{G_{i+1}}{w_i}\right\rfloor - 1\right)\,}\)

(因为右端点 \(-w_i\) 后就是左端点:\(\left\lfloor\frac{G_{i+1}-w_i}{w_i}\right\rfloor = \left\lfloor\frac{G_{i+1}}{w_i}\right\rfloor-1\),此处无取整歧义。)一遍从右到左 \(O(n)\) 求出。

判定:删 \(k\) 可行 \(\iff F_{k-1}\le G_{k+1}\)

删掉第 \(k\) 块后,剩余序列 = 原前缀 \((1\sim k-1)\) 拼上原后缀 \((k+1\sim n)\)。


总结

本题的思维链条是三层"翻译":

\(\text{串约束} \xrightarrow{\text{前缀编码}} \text{二叉树节点} \xrightarrow{\text{补全到深度 60}} \text{对齐叶子区间} \xrightarrow{\text{贪心极值}} \text{一次线性判定}\)

  • 转化把"存在指数多的串方案"压缩成"区间能否按序放置";
  • 前后缀贪心 把 \(O(n^2)\) 的枚举删除复用成 \(O(n)\) 的两次递推;
  • \(F_{k-1}\le G_{k+1}\) 一个不等式完成判定
相关推荐
小白快快跑哦14 小时前
python-字符串全解(六):正则表达式-元字符与转义
python·正则表达式·字符串·元字符·转义
lvwangshu2 天前
川剧复演 题解
动态规划·题解·技巧·性质题·思维好题
小白快快跑哦2 天前
python-字符串全解(六):正则表达式-逻辑运算或
python·正则表达式·字符串·逻辑运算
Tisfy3 天前
LeetCode 2913.子数组不同元素数目的平方和 I:第一题先常数很大的O(n^2)
算法·leetcode·题解·模拟·暴力
Logic1014 天前
C语言/数据结构字符串题解:解压缩“字符+次数“格式——多位数字处理与字符串还原
c语言·数据结构·字符串·模拟·时间复杂度·空间复杂度·算法题
Tisfy4 天前
LeetCode 3723.数位平方和的最大值:构造
数学·算法·leetcode·字符串·题解
Tisfy5 天前
LeetCode 2333.最小差值平方和:排序 + 数学计算(不用二分)
数学·算法·leetcode·题解·贪心·排序
Logic1015 天前
C语言/数据结构字符串题解:统计“chhc“子串权值总和——一次遍历模式匹配
c语言·数据结构·算法·字符串·时间复杂度·空间复杂度·ascii
伟大的车尔尼5 天前
贪心题目:单调递增的数字
贪心