牛客网暑期ACM多校训练营(第九场) F Typing practice(AC自动机)

本文解析了一道关于AC自动机的应用题,题目要求通过构建AC自动机来解决字符串匹配问题,特别关注如何通过最少的操作使某串成为给定串集合之一的后缀。文章详细介绍了AC自动机的构建过程及如何进行后续操作来解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:https://www.nowcoder.com/acm/contest/147/F  牛客网

感谢:https://blog.youkuaiyun.com/LSD20164388/article/details/81778591

题目描述 

Niuniu is practicing typing.

Given n words, Niuniu want to input one of these. He wants to input (at the end) as few characters (without backspace) as possible,
to make at least one of the n words appears (as a suffix) in the text.
Given an operation sequence, Niuniu want to know the answer after every operation.
An operation might input a character or delete the last character.

输入描述:

The first line contains one integer n.
In the following n lines, each line contains a word.
The last line contains the operation sequence.
'-' means backspace, and will delete the last character he typed.

He may backspace when there is no characters left, and nothing will happen.

1 <= n <= 4
The total length of n words <= 100000

The length of the operation sequence <= 100000

The words and the sequence only contains lower case letter.

输出描述:

You should output L +1 integers, where L is the length of the operation sequence.

The i-th(index from 0) is the minimum characters to achieve the goal, after the first i operations.
示例1

输入

复制
2
a
bab
baa-

输出

复制
1
1
0
0
0

说明

"" he need input "a" to achieve the goal.
"b" he need input "a" to achieve the goal.
"ba" he need input nothing to achieve the goal.
"baa" he need input nothing to achieve the goal.
"ba" he need input nothing to achieve the goal.
示例2

输入

复制
1
abc
abcd

输出

复制
3
2
1
0
3

说明

suffix not substring.

题意:给你n个串,n的大小不大于4,再给你一个串,作为询问串,问你在询问串中每多取一个字符作为子串,需要再在该串的后面加几个字母使得四个串中存在一个作为后缀,若取到的是-代表退后一步,即将上一步取到的字母舍去,空串也要考虑。
思路:首先将四个串加入到AC自动机中,利用AC自动机建立树,像是之前写过的题,fail和next正常建立,有一点不同就是当该结点的fail结点end为1时,该节点的end也为1,可以类比DNA那道题的解法,至于为什么这里就不说了。
然后就是很神奇的地方了,end为1的结点我们将他的ans设为1,然后更新上面每个点的ans,怎么更新后面说,ans记录的是这个点要再加几个字符可以变成存在后缀的满足要求的状态。这样的话,最后每个子串的结果就是这个串在树上跑完之后最后
一个结点的ans[]-1。
怎么更新呢,在咱们求最短路的时候,先将直接可以到达的点也就是end==1的点放在队列中,接下来这个点可以直接到达的点的ans[]等于这个点的ans[]+1,什么叫做可以直接到达呢,就是这个点的上一步是谁,回退回去就可以得到谁,利用vectot
存起来。
接下来对于每一步,我们都保存起来最后一步是哪个结点,接下来再一步就是这一步加了一步。
写的不好,估计只有自己能看懂,上面的大佬写的很棒。
代码如下:
#include<queue>
#include<stdio.h>
#include<vector>
#include<iostream>
#include<string.h>
#include<algorithm>

using namespace std;

const int maxn =  4*100000+5;       //字典树上最多有多少个点
const int maxl = 100000+5;       //目标串的最大长度

struct Trie
{
    int Next[maxn][26], fail[maxn], End[maxn];
    int root, L;
    int ans[maxn];
    vector<int>V[maxn];
    queue<int>QQ;
    int res[maxl];
    int new_node()
    {
        for(int i=0; i<26; i++)
            Next[L][i] = -1;
        V[L].clear();
        End[L++] = 0;
        return L-1;
    }
    void init()
    {
        L = 0;
        root = new_node();
        while( !QQ.empty() )
            QQ.pop();
        memset(ans, 0, sizeof(ans));
    }
    void Insert(char buf[])
    {
        int len = strlen(buf);
        int now = root;
        for(int i=0; i<len; i++)
        {
            int tmp = buf[i]-'a';   ///是小写还是大写看题目要求
            if(Next[now][tmp] == -1)
                Next[now][tmp] = new_node();
            now = Next[now][tmp];
        }
        End[now]++;                 ///记录这个点式多少个模式串的子串
        ans[now] = 1;
        QQ.push(now);
    }
    void build()
    {
        queue<int>Q;
        fail[root] = root;
        for(int i=0; i<26; i++)
        {
            if(Next[root][i] == -1)
            {
                Next[root][i] = root;
            }
            else
            {
                fail[Next[root][i]] = root;
                Q.push(Next[root][i]);
            }
        }
        while( !Q.empty() )
        {
            int now = Q.front();
            Q.pop();
            if(End[fail[now]] != 0)
            {
                End[now] = 1;
                ans[now] = 1;
                QQ.push(now);
            }
            for(int i=0; i<26; i++)
            {
                if(Next[now][i] == -1)
                {
                    Next[now][i] = Next[fail[now]][i];
                }
                else
                {
                    fail[Next[now][i]] = Next[fail[now]][i];
                    Q.push(Next[now][i]);
                }
            }
        }
        for(int i=0; i<L; i++)
        {
            for(int j=0; j<26; j++)
            {
                if(Next[i][j] != i)
                {
                    V[Next[i][j]].push_back(i);
                }
            }
        }
        while( !QQ.empty() )
        {
            int f = QQ.front();
            QQ.pop();
            for(int i=0; i<V[f].size(); i++)
            {
                int v = V[f][i];
                if(ans[v]!=0) continue;
                ans[v] = ans[f]+1;
                QQ.push(v);
            }
        }
    }
    void solve(int len , int now , char c)
    {
            now = Next[now][c-'a'];
            res[len] = now;
    }
    void query(char buf[])
    {
        memset(res , 0 , sizeof(res));
//        char tmp[maxl];
        int len = 0;
        int Len = strlen(buf);
        printf("%d\n" , ans[0]-1);
        for(int i=0; i<Len; i++)
        {
            if(buf[i]=='-')
            {
                len--;
            }
            else
            {
                len++;
                solve(len , res[len-1] , buf[i]);
            }
            if(len < 0)
                len = 0;
            printf("%d\n" , ans[res[len]]-1);
        }
    }
};

Trie AC;
char buf[maxl];

int main()
{
    int t, n;
    while( scanf("%d", &n) != EOF )
    {
        AC.init();
        for(int i=0; i<n; i++)
        {
            scanf("%s", buf);
            AC.Insert(buf);
        }
        getchar();
        AC.build();
        gets(buf);
        AC.query(buf);
    }

    return 0;
}

/*
4
sheast
shyash
yaat
easthy
shetsy-hea-ast
4
4
4
3
4
4
3
4
4
3
2
3
2
1
0


2
a
ab
---
1
1
1
1
*/

 

转载于:https://www.cnblogs.com/Flower-Z/p/9496859.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值