『题解』Codeforces 1200E Compress Words
Solution
假设前 \(i - 1\) 个字符串已经进行了合并,组合成了字符串 \(t\),现在要将字符串 \(s\) 与 \(t\) 进行合并。
只需要算出字符串 \(s\) 的前缀函数值,再以此算出 \(t\) 最后一个字符的前缀函数值,就能够知道 \(s\) 与 \(t\) 重合的部分。
考虑使用离线版 KMP
,由于有很多字符串,每次添加一个新的字符串,\(t\) 都会发生变化,于是又要重新计算 \(t\) 的前缀函数值。时间复杂度 \(\mathcal{O}(n \times |s|)\),超时。
因为需要计算 \(s\) 和 \(t\) 的重合部分最长值,相当于计算 \(t\) 最后一个字符的前缀函数值 \(p\),则 \(p\) 定不会超过 \(|s|\),所以只需要 \(t\) 中末尾的 \(|s|\) 个字符,计算这些字符的前缀函数值,这样 \(p\) 能够保证正确,虽然其他 \(|s| - 1\) 个字符的前缀函数值不能保证正确,但是不难发现,它们不影响 \(p\) 的计算。尽管这样也需要做 \(n\) 次前缀函数值的计算,但是整体长度只有 \(10^6\),所以时间复杂度为 \(\mathcal{O}(|s|) = \mathcal{O}(10^6)\)。
Tip
本题每次合并是与前面已经合并完的整个字符串进行合并,而不是与前一个字符串进行合并。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int n;
vector <int> p;
char s[maxn], t[maxn];
void prefix(int len)
{
p[0] = 0;
for (int i = 1; i < len; i ++)
{
int j = p[i - 1];
while (j > 0 && s[i] != s[j])
j = p[j - 1];
if (s[i] == s[j])
j ++;
p[i] = j;
}
}
// 计算 s 和 t 的重合部分,相当于将 t 中 |s| 个字符添加到 s 末尾
int KMP(int len1, int len2)
{
int pre = 0, pos = max(len2 - len1, 0);
if (s[0] == t[pos])
pre ++;
for (int i = pos + 1; i < len2; i ++)
{
int j = pre;
while (j > 0 && t[i] != s[j])
j = p[j - 1];
if (t[i] == s[j])
j ++;
pre = j;
}
return pre;
}
int main()
{
cin >> n;
cin >> t;
int lent = strlen(t);
for (int i = 2; i <= n; i ++)
{
cin >> s;
int lens = strlen(s);
int cnt;
p.resize(lens);
prefix(lens);
cnt = KMP(lens, lent);
for (int j = cnt; j < lens; j ++)
t[lent ++] = s[j];
}
cout << t;
return 0;
}