P5231 [JSOI2012]玄武密码

P5231 [JSOI2012]玄武密码

链接

分析:

  首先对所有询问串建立AC自动机,然后扫描一遍母串,在AC自动机上走,没走到一个点,标记这个点走过了,并且它的fail树上的祖先节点也可以访问到(即可以匹配到主串),于是沿着fail树打标记,当到一个已经打过标记的点的时候,退出。这样保证每个点只会被打标机一次。询问时,在AC自动机上倒着往前走,走到第一个打过标记的点的时候,从AC自动机的根节点到这个点的距离就是答案。复杂度$O(N+100M)$。

代码: 

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<cctype>
#include<set>
#include<queue>
#include<vector>
#include<map>
using namespace std;
typedef long long LL;

inline int read() {
    int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f;
}

const int SZ = 10000005, N = 100005;
int id[105], len[N], pos[N], ch[SZ][4], fa[SZ], q[SZ], fail[SZ], Index;
bool vis[SZ];
char s[SZ], t[105];

void Insert(char *s,int k) {
    int u = 0;
    for (int i = 1; i <= len[k]; ++i) {
        int c = id[(int)s[i]];
        if (!ch[u][c]) ch[u][c] = ++Index, fa[Index] = u;
        u = ch[u][c];
    }
    pos[k] = u;
}
void bfs() {
    int L = 1, R = 0;
    for (int i = 0; i <= 3; ++i) if (ch[0][i]) q[++R] = ch[0][i];
    while (L <= R) {
        int u = q[L ++];
        for (int c = 0; c <= 3; ++c) {
            int v = ch[u][c];
            if (!v) ch[u][c] = ch[fail[u]][c];
            else fail[v] = ch[fail[u]][c], q[++R] = v;
        }
    }
}
int Calc(int x) {
    int ans = len[x];
    for (int i = pos[x]; i; i = fa[i], ans --) 
        if (vis[i]) return ans;
}
int main() {
    int n = read(), m = read();
    id['E'] = 0, id['S'] = 1, id['W'] = 2, id['N'] = 3;
    scanf("%s", s + 1);
    for (int i = 1; i <= m; ++i) {
        scanf("%s", t + 1);
        len[i] = strlen(t + 1);
        Insert(t, i);
    }
    bfs();
    int u = 0, v;
    for (int i = 1; i <= n; ++i) {
        int c = id[(int)s[i]];
        u = ch[u][c];
        v = u;
        while (v && !vis[v]) vis[v] = 1, v = fail[v];
    }
    for (int i = 1; i <= m; ++i) 
        printf("%d
", Calc(i));
    return 0;
}
原文地址:https://www.cnblogs.com/mjtcn/p/10468219.html