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

这道题其实就是一道裸的AC自动机板子题,只需要在建tire图的时候稍微处理一下,因为它求的是子串的最长前缀,那么我们可以将子串再划分为许多长度不同的前缀就行了,然后再记录一下划分出来的前缀分别属于哪一个子串。

代码

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int N = 1e7 + 5;

char str[N], s[N];
int tr[N][4], cnt[N], net[N], idx, mp[200], ans[N];
bool vis[N];
queue<int> q;
vector<int> v, bel[N];

void insert(int x)
{
    int p = 0;
    for (int i = 0; str[i]; i++)
    {
        int t = mp[(int)str[i]];
        if (!tr[p][t])
            tr[p][t] = ++idx;
        p = tr[p][t];
        cnt[p] = i + 1;//记录每一个前缀的长度
        bel[p].push_back(x);//记录当前节点可以更新哪些子串
    }
}

void build()
{
    for (int i = 0; i < 4; i++)
        if (tr[0][i])
            q.push(tr[0][i]);
    while (!q.empty())
    {
        int t = q.front();
        q.pop();
        for (int i = 0; i < 4; i++)
        {
            int p = tr[t][i];
            if (!p)
                tr[t][i] = tr[net[t]][i];
            else 
            {
                net[p] = tr[net[t]][i];
                q.push(p);
            }
        }
    }
}//AC自动机

int main()
{
    mp['N'] = 0, mp['W'] = 1, mp['S'] = 2, mp['E'] = 3;
    int n, m;
    scanf("%d%d", &n, &m);
    scanf("%s", &s);
    for (int i = 1; i <= m; i++)
    {
        scanf("%s", &str);
        insert(i);
    }
    build();
    for (int i = 0, j = 0; s[i]; i++)
    {
        int t = mp[(int)s[i]];
        j = tr[j][t];
        int p = j;
        while (p)
        {
            if (vis[p])
                break;//一个优化,即先把所有的找出来,然后在一起更新
            vis[p] = true;
            v.push_back(p);
            p = net[p];
        }
    }
    for (int i = 0; i < v.size(); i++)
    {
        int p = v[i];
        for (int j = 0; j < bel[p].size(); j++)
            ans[bel[p][j]] = max(ans[bel[p][j]], cnt[p]);//更新答案
    }
    for (int i = 1; i <= m; i++)
        printf("%d\n", ans[i]);
    return 0;
}
posted @ 2021-01-04 12:02  DSHUAIB  阅读(76)  评论(0)    收藏  举报