转动的字节:题解¶
解法与贡献¶
采用出题人的反向逐字符 BFS:边标签变为额外结点标签,通过二进制稳定分组维护字符串顺序,并按前驱编号排列反向邻接。Codex(GPT-6)补充三级目标的证明、按实际规模分配存储和标准 C++17 实现。BFS 与稳定基数排序都是经典方法。
标签块与逐字符距离¶
把原边 \(u\to v\) 的标签存成一个额外块 \(e\) ,连接 \(u\to e\to v\) .原结点也是块,因此共有 \(n+m\) 个块。每个块保存其完整标签、已读取字符数、是否到达以及最先到达时的后继块。
从终点一沿反向边搜索,每次读取当前标签中从右往左的一个字符。读完块后进入其反向邻居。每一轮恰好读取一个字符,故 BFS 轮数就是已读的后缀长度。无需把每个字符单独建成图顶点;一个块内部的字符次序是固定的,可以仅保存一个下标。
所有标签非空,最短路径不含原图结点环;否则去掉环会减少字符数。这既保证答案可简单地重建,也说明较长的终点后缀永远不能帮助最短路径。
第二目标:在前面增加字符的稳定排序¶
反向读取并不意味着比较反向字符串。已经读到的字符始终表示其正向顺序的后缀;下一步把新字符加到这个后缀前面。
假设同一轮的状态已按它们代表的后缀字典序排列。下一轮的字符串形如 \(cS\) ,其中 \(c\) 为新字符、\(S\) 为上一轮后缀。将所有新状态稳定地分为新字符零与新字符一两组:先处理零组,再处理一组;组内保留此前的状态顺序。它恰好按 \((c,S)\) 排序,因此也按新的正向后缀排序。初始只有终点标签的最后一个字符,归纳成立。
实现使用当前轮和下一轮各两个队列,共四个队列。不能把所有零字符边当作零费用边;每个字符都花费一轮。
第三目标:反向结点编号序列¶
把原结点 \(v\) 的反向入边按原前驱编号 \(u\) 升序排列。进入边块时,就可以把其原前驱编号视为加入反向编号序列;随后边块和结点块内部不再分叉。
考虑两条长度和完整字符串均相同的最优路径。如果它们经过某个相同原结点,各自从该结点到终点的部分都必须是该点的最短后缀;否则换成更短后缀会使完整路径更短。两段后缀长度因此相同。再由完整字符串相同,等长的终点后缀字符也相同。
现在从终点追踪两条路径,直到反向结点序列的第一个分叉。在分叉之前,各共同结点到达于相同字符层,且后缀相同。平行边若仍保持相同结点序列,它们在两条最优路径中也必须有相同长度和相同标签,否则无法保持相同的最短后缀。因而真正的第一次编号分叉发生在同一个状态,较小的前驱编号先入队。
此后,两条最终相同字符串在每个字符层都有相同的已读后缀,稳定分组不会颠倒它们的顺序。因此较小的反向编号序列先完成。若一个反向编号序列是另一个的真前缀,继续走至少再读一个非空标签,无法仍是相同长度的最优路径,所以不会破坏这一比较。
这也解释了为什么标签长短不同不会使第三目标失效:需要比较的最优路径到共同原结点的后缀距离相同,不能用较晚到达的同一结点参与平局。
为什么可以首次到达就标记¶
对于同一个块,较早轮次到达意味着更短的后缀。同轮到达时,上述队列顺序给出字典序更小的后缀;字符相同时,第三目标给出更小的反向编号序列。块内部剩余字符完全相同,且任何到该块的前半条路径都能接上这条更优后缀,因此后来到达被支配,可以舍弃。
对两个相同块后缀比较反向结点序列时,它们以同一个原结点结束;简单最短路径不会出现一条是真前缀的情形。因此将相同的前半路径接在后面不会颠倒编号序列顺序。
记录首次到达的 From 指针。从第一个完整读完标签的可选起点沿指针到一,依次输出各块的原始正向标签,再输出其中属于原图的结点编号。这就是三级目标最优答案;不能在刚进入起点块时提前结束,因为其剩余字符也计入长度与字典序。
邻接顺序与复杂度¶
为线性构建所需顺序,先把原边按起点分桶。按原起点编号从大到小扫描,把对应反向边插到链表头;最终每个原结点的前驱顺序就是升序。同一起终点间的平行边保持任意顺序都可以:字符不同会由稳定字符分组区分,字符相同则两项输出完全一样。
记 \(L\) 为全部点标签与边标签的字符总数。每个块首次进入一次、每个块内字符处理一次、每条转换后的边检查一次,所以时间为 \(O(n+m+L)\) ,空间为 \(O(n+m+L)\) .本题每个标签长度至多五,可将时间与空间都写成 \(O(n+m)\) ;实现为每块固定保存五个字符,不额外建立逐字符图。四个队列存块编号,并复用容量。
参考程序保留上述反向线性算法。另一种核验方式是先用 Dijkstra 求后缀距离,在最短路图上正向筛选最小字符串,再用匹配该字符串的可达性反向选择最小前驱;它将三个目标分开,但需要堆和额外的遍历,最坏时间增加为 \(O((n+m)\log n+L)\) ,不作为本题的首选计算程序。