题解 Luogu P5231 【[JSOI2012]玄武密码】

做法 :AC自动机

我们考虑把 \(M\) 个询问子串建成一棵 \(trie\) 树,构建出 \(fail\) 指针,用长串 \(S\)AC自动机 上进行匹配,根据 \(fail\) 指针的定义,可知对于长串 \(S\) 遍历 \(trie\) 树时遍历过的结点以及此结点通过 \(fail\) 指针跳到的结点 \(u\),在 \(trie\) 树上 \(1\)\(u\) 的路径上的字符串一定存在于长串 \(S\) 上,即某个串的前缀 \(1 … u\) 存在于长串 \(S\) 上,故我们可以在结点 \(u\) 上打标记,再重新遍历每个询问字符串在 \(trie\) 树上的结点,拥有标记即可更新答案。

\(Code:\)

#include <bits/stdc++.h>

const int MaxN = 1e7 + 10;
const int MaxM = 1e5 + 10;
const int MaxLM = 1e2 + 10;

using namespace std;

inline int read() {
    int cnt = 0, opt = 1;
    char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar())
        if (ch == '-') opt = 0;
    for (; ch >= '0' && ch <= '9'; ch = getchar())
        cnt = (cnt << 3) + (cnt << 1) + (ch ^ 48);

    return opt ? cnt : -cnt;
}

inline int modify(char s) {
    if (s == 'E') return 0;
    if (s == 'S') return 1;
    if (s == 'W') return 2;
    if (s == 'N') return 3;
}

int n, m;
char s[MaxN], s2[MaxM][MaxLM];
int ch[MaxLM * MaxM][4], tot = 1;
int nxt[MaxN], bo[MaxN];
queue <int> que;

inline void insert(int num) {
    int len = strlen(s2[num] + 1), u = 1;
    for (int i = 1; i <= len; ++ i) {
        int str = modify(s2[num][i]);
        if (! ch[u][str]) ch[u][str] = ++ tot;
        u = ch[u][str];
    }
}//把当前字符串插入 trie 树

inline void make_fail() {
    for (int i = 0; i <= 3; ++ i)
        ch[0][i] = 1;
    que.push(1), nxt[1] = 0;

    while (que.size()) {
        int u = que.front();
        que.pop();

        for (int i = 0; i <= 3; ++ i) {
            if (! ch[u][i]) ch[u][i] = ch[nxt[u]][i];
            else {
                que.push(ch[u][i]);
                int v = nxt[u];
                while (v > 1 && ! ch[v][i]) v = nxt[v];
                nxt[ch[u][i]] = ch[v][i];
            }
        }

    }
}//构建 fail 指针

inline void s_find() {
    int u = 1;
    for (int i = 1; i <= n; ++ i) {
        int str = modify(s[i]);
        u = ch[u][str];
        int k = u;
        while (k > 1 && ! bo[k]) {//若 k 结点打过标记,则所有 k 的 fail 指针跳到的结点都打过标记,故可以跳过
            bo[k] = 1;
            k = nxt[k];
        }
    }
}//把在 Trie 树上与 s 串匹配的位置打上标记

inline int solve(int num) {
    int len = strlen(s2[num] + 1), u = 1;
    int ans = 0; 
    for (int i = 1; i <= len; ++ i) {//遍历当前字符串在 trie 树上的每一个结点
        int str = modify(s2[num][i]);
        u = ch[u][str];
        if (bo[u])//存在标记就更新答案
            ans = i;
    }
    return ans;
}//找出前缀最大匹配

int main() {
    n = read(), m = read();
    scanf("%s", s + 1);

    for (int i = 1; i <= m; ++ i) {
        scanf("%s", s2[i] + 1);
        insert(i);
}

    make_fail();
    s_find();

    for (int i = 1; i <= m; ++ i)
        printf("%d\n", solve(i));
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/chz-hc/p/12340218.html
今日推荐