UVa 1227 The Longest Constant Gene
题目分析
本题要求我们在 NNN 个基因组字符串中找出最长的公共子串(Longest Common Substring\texttt{Longest Common Substring}Longest Common Substring)。每个基因组由字符 A\texttt{A}A、C\texttt{C}C、G\texttt{G}G、T\texttt{T}T 组成,我们需要找到一个最长的连续子串,使得该子串出现在所有 NNN 个基因组中。
输入输出规格
- 输入:数据组数 TTT(T≤20T \leq 20T≤20),每组数据包含 NNN(2≤N≤62 \leq N \leq 62≤N≤6)个字符串,每个字符串长度不超过 1,000,0001,000,0001,000,000。
- 输出:每组数据输出最长公共子串的长度。
问题复杂度分析
由于字符串长度可能达到 1,000,0001,000,0001,000,000,而 NNN 很小,我们需要设计一个高效的算法。暴力枚举所有子串的复杂度为 O(L2)O(L^2)O(L2),显然不可行。
解法一:后缀自动机(Suffix Automaton\texttt{Suffix Automaton}Suffix Automaton)
算法思想
后缀自动机是一种能够高效处理字符串匹配问题的数据结构。对于本题,我们可以:
- 对第一个字符串构建后缀自动机
- 对于其他每个字符串,在自动机上运行并记录每个状态的最大匹配长度
- 对于每个状态,取在所有字符串中的最小匹配长度
- 最终答案为所有状态的最小匹配长度中的最大值
算法步骤
-
构建后缀自动机:
- 使用最短的字符串构建后缀自动机,减少状态数量
- 每个状态包含 len\texttt{len}len(接受的最长子串长度)、link\texttt{link}link(后缀链接)和 next\texttt{next}next 数组(状态转移)
-
多字符串匹配:
- 对于每个其他字符串,在后缀自动机上运行
- 维护当前状态和当前匹配长度
- 当无法匹配时,通过后缀链接回溯
-
传播匹配信息:
- 按状态长度排序,从长到短传播匹配长度
- 确保父状态能够获得子状态的匹配信息
-
计算最终答案:
- 对每个状态,取其在所有字符串中的最小匹配长度
- 找出所有状态中的最大值
复杂度分析
- 构建自动机:O(L1)O(L_1)O(L1)
- 每个字符串匹配:O(Li)O(L_i)O(Li)
- 总复杂度:O(∑Li)=O(N×L)O(\sum L_i) = O(N \times L)O(∑Li)=O(N×L)
解法二:DC3\texttt{DC3}DC3 算法构建后缀数组
算法思想
DC3\texttt{DC3}DC3 算法是一种线性时间构建后缀数组的方法。结合后缀数组和高度数组(LCP\texttt{LCP}LCP),我们可以高效解决最长公共子串问题。
算法步骤
-
字符串预处理:
- 将所有字符串连接,用特殊字符分隔
- 将 G\texttt{G}G 映射为 B\texttt{B}B,T\texttt{T}T 映射为 D\texttt{D}D,减少字符集大小
-
构建后缀数组:
- 使用 DC3\texttt{DC3}DC3 算法在 O(n)O(n)O(n) 时间内构建后缀数组
- 同时计算每个后缀在原数组中的位置
-
构建高度数组(LCP\texttt{LCP}LCP):
- 根据后缀数组计算相邻后缀的最长公共前缀
-
滑动窗口求解:
- 使用滑动窗口技术在高度数组上寻找覆盖所有 NNN 个字符串的最小区间
- 维护区间内 LCP\texttt{LCP}LCP 的最小值,并跟踪最大值
DC3\texttt{DC3}DC3 算法核心
DC3\texttt{DC3}DC3 算法采用分治策略:
- 将后缀分为三类:模 333 余 000、余 111、余 222
- 递归处理模 333 余 111 和余 222 的后缀
- 合并三类后缀的排序结果
复杂度分析
- 构建后缀数组:O(n)O(n)O(n)
- 构建高度数组:O(n)O(n)O(n)
- 滑动窗口:O(n)O(n)O(n)
- 总复杂度:O(n)O(n)O(n),其中 nnn 为连接后字符串的总长度
代码实现
解法一:后缀自动机实现
// The Longest Constant Gene
// UVa ID: 1227
// Verdict: Accepted
// Submission Date: 2025-10-25
// UVa Run Time: 0.900s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cstring>using namespace std;const int MAXN = 1000010;// 后缀自动机结构体
struct SuffixAutomaton {// 状态节点结构体struct State {int len; // 该状态能够接受的最长子串长度int link; // 后缀链接,指向更短的后缀状态int next[4]; // 状态转移数组,4 个字符(A, C, G, T)};vector<State> st; // 状态数组int sz; // 当前状态数量int last; // 最后一个状态// 构造函数,初始化后缀自动机SuffixAutomaton(int maxSize) {