题解:LGP2292 [HNOI2004] L 语言

LGP2292 [HNOI2004] L 语言

题目

给定 \(n(\le 20)\) 个字符串 \(s_i(|s_i|\le 20)\) 构成集合 \(A\)\(m(\le 50)\) 个字符串 \(t_i(|t_i|\le 2\times 10^6)\)

定义一个字符串为合法,当且仅当该字符串可以划分成几个子串,使得每个子串都属于 \(A\)

对每个 \(t\) 求出最长合法前缀的长度。

题解

Part 1. DP

\(f(i)\) 表示 \(t\) 的前缀 \([1,i]\) 是否合法,其中 \(f(0)=1\),于是,

\(f(i) = \max\limits_{j=1}^{n} f(i-|s_j|)\land [t[i-|s_j|+1,i]=s_j]\)

直接转移,再用哈希或者其他方法做到 \(O(1)\) 比较字符串,总复杂度 \(O(m|t|n)\)

Part 2. AC自动机

考虑用 AC自动机 做多模式匹配的时候,我们从 \(1\)\(n\) 遍历文本串 \(t\),然后对于每个 \(i\),从 \(i\) 不断跳 \(fail\) 指针(即 \(i \to fail_i\)),那么每次跳到的节点(假设存在这个点代表的单词),就表示 \(t\)\(i\) 结尾存在这样一个子串。

那么先对 \(A\) 建出 AC自动机,然后转移 \(f\) 时,对于每个 \(i\),反复跳 \(fail\),和 Part 1. 一样的转移。

然而这样最坏复杂度和上面一样。

Part 3. 状态压缩

对于每个 \(i\),我们想知道的是 \(i\) 对应的 \(fail\) 链上存在的所有单词的长度有哪些,注意到 \(|s|\le 20\),那么可以状压这条链的信息,用 \(2^x\) 表示该链上长度为 \(x\) 的单词是否存在。

这个可以在求 \(fail\) 的 BFS 时处理,也就是说节点 \(u\) 上的状压信息储存的是 \(u\) 到根的 \(fail\) 链上的信息。

并且每个 \(f(i)\) 最多只会由前 \(20\)\(f(j)\) 转移过来,那么我们便可以状压前 \(20\)\(f\) 的信息,每次通过左移来"滚动数组"。

具体来说,设 \(g\) 的第 \(x\) 位(从 \(0\) 开始)表示 \(f(i-x)\) 的状态。记我们在 \(fail\) 链上的信息为 \(h\),那么每次只需要判断 \(h \& g\) 是否为 \(0\) 即可,如果不为 \(0\) 就表示 \(f(i)=1\),那么将 \(g\) 的第 \(0\) 位设为 \(1\)。然后 \(i+1\) 的同时 \(g\) 左移一位即可。

\(g\) 可以用 unsigned int,方便滚动。

复杂度 \(O(m|t|)\)

代码

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using ULL = unsigned long long;

const int N = 405;

int n, m;
string s, t;
int trie[N][26], cnt[N], fail[N], sta[N], dep[N], idx;

void insert(string s) {
    int cur = 0;
    for (char c : s) {
        if (!trie[cur][c - 'a']) trie[cur][c - 'a'] = ++idx;
        cur = trie[cur][c - 'a'];
    }
    cnt[cur]++;
}

void build() {
    queue<int> q;
    for (int i = 0; i <= 25; i++) {
        if (trie[0][i]) {
            q.push(trie[0][i]);
            dep[trie[0][i]] = 1;
        }
    }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        sta[u] = sta[fail[u]];
        if (cnt[u]) sta[u] |= 1 << dep[u];
        for (int i = 0; i <= 25; i++) {
            if (trie[u][i]) {
                fail[trie[u][i]] = trie[fail[u]][i];
                q.push(trie[u][i]);
                dep[trie[u][i]] = dep[u] + 1;
            } else {
                trie[u][i] = trie[fail[u]][i];
            }
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> s;
        insert(s);
    }
    build();
    while (m--) {
        cin >> t;
        unsigned int f = 1;
        int cur = 0, ans = 0;
        for (int i = 0; i < t.size(); i++) {
            cur = trie[cur][t[i] - 'a'];
            f <<= 1;
            if (sta[cur] & f) {
                ans = i + 1;
                f |= 1;
            }
        }
        cout << ans << '\n';
    }
    
    return 0;
}
posted @ 2025-02-08 18:37  chenwenmo  阅读(14)  评论(0)    收藏  举报