『题解』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;
}
posted @ 2023-03-11 21:09  Clyfort  阅读(21)  评论(0编辑  收藏  举报