P2292 [HNOI2004] L 语言 题解 AC自动机 + 状态压缩 + dp

题目链接:https://www.luogu.com.cn/problem/P2292

题目大意:

给定 \(n(\le 20)\) 个模式串 \(s_i(|s_i| \le 20)\),有 \(m(\le 50)\) 次询问,每次询问给出一个字符串 \(t(|t| \le 10^6)\)

对于每次询问的字符串 \(t\),你需要回答出它的由模式串拼成(模式串可以重复使用)的最长前缀的长度。

解题思路:

80分

\(f_i\) 表示能不能匹配到第 \(i\) 个位置,然后枚举 \(t\) 的每一个位置,跑一遍 AC自动机进行匹配。

时间复杂度 \(O(mST)\)(其中 \(S\) 表示 \(s_i\) 的最大长度;\(T\) 表示 \(t\) 的最大长度)。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;

struct Node {
    int son[26], fail;
    bool flag;
    Node() {
        memset(son, 0, sizeof(son));
        fail = 0;
        flag = false;
    }
} tr[maxn];
int n, m, cnt;
char s[22][22], t[maxn];
bool f[maxn];

void ins(char *s) {
    int u = 0;
    for (; *s; s++) {
        int c = (*s) - 'a';
        if (!tr[u].son[c])
            tr[ tr[u].son[c] = ++cnt ] = Node();
        u = tr[u].son[c];
    }
    tr[u].flag = true;
}

void build() {
    queue<int> que;
    for (int i = 0; i < 26; i++)
        if (tr[0].son[i])
            que.push(tr[0].son[i]);
    while (!que.empty()) {
        int u = que.front();
        que.pop();
        for (int i = 0; i < 26; i++) {
            int v = tr[u].son[i];
            if (!v) continue;
            int x = tr[u].fail;
            while (x && !tr[x].son[i])
                x = tr[x].fail;
            tr[v].fail = tr[x].son[i];
            que.push(v);
        }
    }
}

bool vis[maxn];
void cal(char *s, int p) {
    int u = 0;
    for (int i = p; *s; s++, i++) {
        int c = (*s) - 'a';
        if (tr[u].son[c])
            u = tr[u].son[c];
        else
            break;
        f[i] |= tr[u].flag;
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%s", s[i]);
        ins(s[i]);
    }
    for (int i = 0; i < m; i++) {
        scanf("%s", t);
        int len = strlen(t);
        memset(f, 0, sizeof(bool) * (len+1));
        for (int j = 0; j < len; j++) {
            if (j && !f[j-1])
                continue;
            cal(t+j, j);
        }
        int ans = 0;
        for (int i = len-1; i >= 0; i--) {
            if (f[i]) {
                ans = i+1;
                break;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

100分

考虑在字典树每一个节点维护一个二进制状态,状态的第 \(i\) 位为 \(1\) 表示存在一个长度为 \(i\) 的以当前节点结尾的后缀。

示例程序:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e6 + 5;

struct Node {
    int son[26], fail, d, s;
    bool flag;
    Node() {d = 0;}
    Node(int _d) {
        d = _d;
        s = 0;
        memset(son, 0, sizeof(son));
        fail = 0;
        flag = false;
    }
} tr[100010];
int n, m, cnt;
char s[22], t[maxn];
bool f[maxn];

void ins(char *s) {
    int u = 0;
    for (int d = 1; *s; d++, s++) {
        assert(d <= 20);
        int c = (*s) - 'a';
        if (!tr[u].son[c])
            tr[ tr[u].son[c] = ++cnt ] = Node(d);
        u = tr[u].son[c];
    }
    tr[u].s = 1 << tr[u].d;
    tr[u].flag = true;
}

void build() {
    queue<int> que;
    for (int i = 0; i < 26; i++)
        if (tr[0].son[i])
            que.push(tr[0].son[i]);
    while (!que.empty()) {
        int u = que.front();
        que.pop();
        for (int i = 0; i < 26; i++) {
            int v = tr[u].son[i];
            if (!v) continue;
            int x = tr[u].fail;
            while (x && !tr[x].son[i])
                x = tr[x].fail;
            tr[v].fail = tr[x].son[i];
            que.push(v);
        }
    }
}

void bfs() {
    queue<int> que;
    que.push(0);
    while (!que.empty()) {
        int u = que.front();
        que.pop();
//        for (int v = tr[u].fail; v; v = tr[v].fail)
//            if (tr[v].flag)
//                tr[u].s |= 1 << tr[v].d;

        int v = tr[u].fail;
        tr[u].s |= tr[v].s;

        for (int i = 0; i < 26; i++) {
            int v = tr[u].son[i];
            if (v) que.push(v);
        }
    }
}

int main() {

    tr[0] = Node(0);

    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%s", s);
        ins(s);
    }
    build();
    bfs();

    while (m--) {
        scanf("%s", t + 1);
        int len = strlen(t + 1);
        memset(f, 0, sizeof(bool) * (len+1));
        f[0] = true;
        int ans = 0, tmp = 1;
        for (int i = 1, u = 0; i <= len; i++) {
            int c = t[i] - 'a';
            while (u && !tr[u].son[c])
                u = tr[u].fail;
            if (tr[u].son[c])
                u = tr[u].son[c];
            tmp <<= 1;
            f[i] = tmp & tr[u].s;
            if (f[i]) {
                ans = i;
                tmp |= 1;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}
posted @ 2023-09-06 04:47  quanjun  阅读(17)  评论(0编辑  收藏  举报