题解:洛谷 P1470 最长前缀
【题目来源】
洛谷:P1470 [USACO2.3] 最长前缀 Longest Prefix - 洛谷
【题目描述】
在生物学中,一些生物的结构是用包含其要素的大写字母序列来表示的。生物学家对于把长的序列分解成较短的序列(即元素)很感兴趣。
如果一个集合 \(P\) 中的元素可以串起来(元素可以重复使用)组成一个序列 \(s\) ,那么我们认为序列 \(s\) 可以分解为 \(P\) 中的元素。元素不一定要全部出现(如下例中 BBC 就没有出现)。举个例子,序列 ABABACABAAB 可以分解为下面集合中的元素:{A,AB,BA,CA,BBC}
序列 \(s\) 的前面 \(k\) 个字符称作 \(s\) 中长度为 \(k\) 的前缀。设计一个程序,输入一个元素集合以及一个大写字母序列,设 \(s′\) 是序列 \(s\) 的最长前缀,使其可以分解为给出的集合 \(P\) 中的元素,求 \(s′\) 的长度 \(k\)。
【输入】
输入数据的开头包括若干个元素组成的集合 \(O\),用连续的以空格分开的字符串表示。字母全部是大写,数据可能不止一行。元素集合结束的标志是一个只包含一个 . 的行,集合中的元素没有重复。
接着是大写字母序列 \(s\) ,长度为,用一行或者多行的字符串来表示,每行不超过 \(76\) 个字符。换行符并不是序列 \(s\) 的一部分。
【输出】
只有一行,输出一个整数,表示 \(S\) 符合条件的前缀的最大长度。
【输入样例】
A AB BA CA BBC
.
ABABACABAABC
【输出样例】
11
【核心思想】
-
问题分析:给定一个单词集合 \(P\) 和一个目标字符串 \(s\),要求找到 \(s\) 的最长前缀,使其可以被 \(P\) 中的单词拼接而成(单词可重复使用)。这是一个字符串拼接 + 动态规划问题。
-
算法选择:
- 方法一:DP + 直接匹配:\(f[i]\) 表示前 \(i\) 个字符能否被表示,对每个位置枚举所有单词检查是否匹配
- 方法二:DP + KMP 预处理:先用 KMP 算法预处理每个单词在 \(s\) 中的所有匹配位置,再用 DP 转移
-
关键步骤:
- 读入数据:读取单词集合(以
.结束),再读取目标字符串(可能多行) - 方法一(直接匹配):
- 初始化 \(f[0] = 1\)(空串可表示)
- 遍历 \(i\) 从 \(1\) 到 \(len\):
- 遍历每个单词 \(s[j]\):
- 若 \(i \ge |s[j]|\) 且 \(f[i - |s[j]|] = 1\) 且
str.substr(i-|s[j]|, |s[j]|) == s[j]:- \(f[i] = 1\),更新 \(ans = i\),跳出内层循环
- 若 \(i \ge |s[j]|\) 且 \(f[i - |s[j]|] = 1\) 且
- 遍历每个单词 \(s[j]\):
- 方法二(KMP 优化):
- 对每个单词 \(p[c]\) 执行 KMP,预处理
pl[c][i]表示该单词在 \(s\) 的位置 \(i\) 结束处是否匹配 - DP 转移:\(dp[i] = dp[i] \lor dp[i - len[j]]\)(若单词 \(j\) 在位置 \(i\) 匹配)
- 从后往前找最大的 \(i\) 使 \(dp[i] = 1\)
- 对每个单词 \(p[c]\) 执行 KMP,预处理
- 输出:最长可表示前缀长度 \(ans\)
- 读入数据:读取单词集合(以
-
时间/空间复杂度:
- 方法一:\(O(len \cdot |P| \cdot L)\),\(L\) 为单词最大长度,直接子串比较
- 方法二:\(O(c \cdot (n + L) + c \cdot n)\),KMP 预处理 \(O(c \cdot n)\),DP 转移 \(O(c \cdot n)\)
- 空间复杂度:\(O(n)\) 或 \(O(c \cdot n)\)
-
动态规划的核心思想:
- 状态定义:\(f[i]\) 表示前 \(i\) 个字符能否被单词集合表示,具有最优子结构
- 转移方程:\(f[i] = \bigvee_{j} (f[i - |s_j|] \land \text{match}(s_j, str[i-|s_j|..i-1]))\)
- KMP 加速匹配:避免每次 \(O(L)\) 的子串比较,将单次匹配降至 \(O(n)\)
- 前缀特性:只关心最长前缀,因此 DP 按顺序处理,遇到不可表示的位置后续仍可继续尝试
- 适用于单词拆分、字符串拼接、模式匹配类问题
【解题思路】

【算法标签】
普及+ #KMP
【代码详解】
#include <bits/stdc++.h>
using namespace std;
string s[210]; // 存储单词的数组
string str; // 存储输入的目标字符串
bool f[200010]; // 动态规划数组,f[i]表示前i个字符能否被单词组合
int main()
{
int k;
// 读取单词列表,直到遇到"."结束
for (k = 1; ; k++)
{
string ss;
cin >> ss;
if (ss == ".")
{
break;
}
s[k] = ss;
}
// 读取目标字符串(可能有多行)
string ss;
while (cin >> ss)
{
str += ss;
}
// 初始化动态规划数组
f[0] = 1; // 空字符串可以被表示
int ans = 0;
int len = str.size();
// 动态规划处理
for (int i = 1; i <= len; i++)
{
for (int j = 1; j < k; j++)
{
int l = s[j].size(); // 当前单词的长度
// 检查前i-l个字符能否被表示,且当前子串是否匹配单词
if (i >= l && f[i - l] && s[j] == str.substr(i - l, l))
{
f[i] = 1; // 标记前i个字符可以被表示
ans = i; // 更新最大可表示长度
break; // 找到一个匹配即可
}
}
}
// 输出结果
cout << ans << endl;
return 0;
}
// 使用KMP算法再写一遍
#include <bits/stdc++.h>
using namespace std;
// 全局变量声明
int c, n; // c: 模式串数量,n: 目标串长度
int len[205]; // 存储每个模式串的长度
int k[205][15]; // KMP算法的next数组
bool pl[205][200005]; // pl[i][j]表示模式串i在目标串j位置有匹配
bool dp[200005]; // dp[i]表示目标串前i个字符能否被模式串组合
string s, p[205]; // s: 目标串,p: 模式串数组
/**
* KMP算法预处理和匹配
* @param c 当前处理的模式串索引
*/
void kmp(int c)
{
string p1 = p[c]; // 当前模式串
// 初始化next数组
k[c][0] = k[c][1] = 0;
// 计算next数组
for (int i = 2, j = 0; i <= len[c]; i++)
{
while (j && p1[i] != p1[j + 1])
{
j = k[c][j];
}
if (p1[i] == p1[j + 1])
{
j++;
}
k[c][i] = j;
}
// 在目标串中进行模式匹配
for (int i = 1, j = 0; i <= n; i++)
{
while (j && s[i] != p1[j + 1])
{
j = k[c][j];
}
if (s[i] == p1[j + 1])
{
j++;
}
if (j == len[c]) // 找到完整匹配
{
pl[c][i] = 1; // 标记匹配位置
}
}
}
int main()
{
// 读取模式串,直到遇到"."结束
for (c = 1; ; c++)
{
string ss;
cin >> ss;
if (ss == ".")
{
break;
}
p[c] = ss;
len[c] = p[c].size();
p[c] = '0' + p[c]; // 添加前缀方便索引
}
c--; // 调整模式串数量
// 读取目标串(可能有多行)
string ss;
while (cin >> ss)
{
s += ss;
}
n = s.size();
s = '0' + s; // 添加前缀方便索引
// 对每个模式串执行KMP算法
for (int i = 1; i <= c; i++)
{
kmp(i);
}
// 动态规划处理
dp[0] = 1; // 空串可以被表示
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= c; j++)
{
if (pl[j][i]) // 如果模式串j在位置i有匹配
{
dp[i] = dp[i] || dp[i - len[j]]; // 状态转移
}
}
}
// 从后往前查找最大可表示长度
for (int i = n; i >= 1; i--)
{
if (dp[i])
{
cout << i << endl;
return 0;
}
}
// 如果没有找到,输出0
cout << 0;
return 0;
}
【运行结果】
A AB BA CA BBC
.
ABABACABAABC
11
浙公网安备 33010602011771号