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

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

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

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.

题意:给你最多4个字符串(长度<=1e5),然后给你一个操作序列(txt字符串)。是这样的:

你每输入一个字符(刚开始空的字符串也算),就输出至少要在你当前输入的字符串上后面补几个字母才能使前面给的串正好是你补完之后的串的后缀。而且操作序列中包含'-',就是退格,删除你前一个输入的字符。如果当前字符串是空的,那么什么也没发生,当做空串处理。

思路:比赛时想开4个kmp,每个记录nex数组,然后每输入一个字符就开始更新。。。结果遇到形如aaaa,aaaab-b-b-b-这样的复杂度就会退化为n*m,然后就不会了。其实这个思路是可行的,不过需要优化(有大神用了另一个数组,保证失败以后一定到达一个不同的字母的下一个地方,这样就可以了)。还有就是题解说是字典树,貌似也能搞,不太清楚。而我赛后看了一个AC自动机的代码,非常神奇,理解之后自己又去敲了一遍。其实就是利用了ac自动机建图的特点,先对4个单词建图,然后将图中是单词尾节点的节点和失配指针指向的是单词尾节点的节点都标记ans[i]=1并加入队列中。然后对于每个队列的节点u,往上跳其父节点v,则ans[v]=ans[u]+1。再将v加入队列中。ans值不为0的当然就直接跳过。最后查询的时候,从根节点开始,每出入一个字符,就沿AC自动机的根节点往下走,假设对应的节点下标是v,则答案就是ans[v]-1。(因为正好差最少ans[v]-1个字符走到尾节点,仔细想想是不是)然后套个AC自动机的模板就可以了。注意细节的修改。这样时间复杂度应该是O(N*L+M)的自己xjb乱算的。其实这道题主要在于思维,工具就是KMP、字典树和AC自动机。只要善于理解运用,这道题就很容易解决了。以后还要多加练习。。。

代码:

#include<bits/stdc++.h>
using namespace std;
const int mx=400100;
const int ssize=26;
int ans[mx],re[mx];
queue<int>qq;
vector<int>vc[mx];
struct ACzdj{
    int ch[mx][ssize];
    int val[mx];
    int f[mx];
    int last[mx];
    int cnt[mx];
    int sz;
    void init()
    {
     memset(ch[0],0,sizeof(ch[0]));
     memset(cnt,0,sizeof(cnt));
     val[0]=0;
     f[0]=last[0]=0;
     sz=1;
    }
    void insert(char *s)
    {
        int n=strlen(s),u=0;
        for(int i=0;i<n;i++)
        {
            int id=s[i]-'a';
            if(ch[u][id]==0) {
             ch[u][id]=sz;
             memset(ch[sz],0,sizeof(ch[sz]));
             val[sz++]=0;
            }
            u=ch[u][id];
        }
        val[u]=1;
    }
    void getFail()
    {
        queue<int> q;
        f[0]=0;
        for(int i=0;i<ssize;i++)
        {
         int u=ch[0][i];
         if(u)
         {
             last[u]=f[u]=0;
             q.push(u);
         }
        }
        while(!q.empty())
        {
            int r=q.front();q.pop();
            for(int i=0;i<ssize;i++)
            {
                int u=ch[r][i];
                if(!u) {ch[r][i]=ch[f[r]][i];continue;}// !!!
                q.push(u);
                int v=f[r];
                while(v&&ch[v][i]==0) v=f[v];
                f[u]=ch[v][i];
                last[u]=val[f[u]]?f[u]:last[f[u]];
            }
        }
        for(int i=0;i<sz;i++)
        {
            if(val[f[i]]) val[i]=1;
            if(val[i])
            {
                ans[i]=1;
                qq.push(i);
            }
        }

        for(int i=0;i<sz;i++)
        for(int j=0;j<26;j++)
        vc[ch[i][j]].push_back(i);
    }
}ac;
char txt[mx];
char ss[5][mx];
int main()
{
    int T;
    int n,m;
    while(scanf("%d",&n)!=EOF)
    {
        while(!qq.empty())qq.pop();
        for(int i=0;i<mx;i++)vc[i].clear();
        memset(ans,0,sizeof(ans));
        ac.init();
        for(int i=1;i<=n;i++)
        {
            scanf("%s",ss[i]);
            ac.insert(ss[i]);
        }
        ac.getFail();
        while(!qq.empty())
        {
            int u=qq.front();qq.pop();
            for(int i=0;i<vc[u].size();i++)
            {
                int v=vc[u][i];
                if(ans[v]) continue;
                ans[v]=ans[u]+1;
                qq.push(v);
            }
        }
        scanf("%s",txt);
        int len=strlen(txt);
        int f=0;
        printf("%d\n",ans[re[f]]-1);
        for(int i=0;i<len;i++)
        {
            if(txt[i]=='-')
            {
                if(f) f--;
                printf("%d\n",ans[re[f]]-1);
            }
            else
            {
                int k=ac.ch[re[f]][txt[i]-'a'];
                re[++f]=k;
                printf("%d\n",ans[re[f]]-1);
            }
        }
    }
    return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值