USACO 2015 Feb Gold 检查 (AC自动机+栈)

【USACO 2015 Feb Gold】检查

问题描述

FJ把杂志上所有的文章摘抄了下来并把它变成了一个长度不超过10^5的字符串S。他有一个包含n个单词的列表,列表里的n个单词记为t_1…t_N。他希望从S中删除这些单词。
FJ每次在S中找到最早出现的列表中的单词(最早出现指该单词的开始位置最小),然后从S中删除这个单词。他重复这个操作直到S中没有列表里的单词为止。注意删除一个单词后可能会导致S中出现另一个列表中的单词
FJ注意到列表中的单词不会出现一个单词是另一个单词子串的情况,这意味着每个列表中的单词在S中出现的开始位置是互不相同的
请帮助FJ完成这些操作并输出最后的S

输入格式

第一行包含一个字符串S
第二行包含一个整数N
接下来的N行,每行包含一个字符串,第i行的字符串是t_i

输出格式

一行,输出操作后的S

样例输入

begintheescapexecutionatthebreakofdawn
2
escape
execution

样例输出

beginthatthebreakofdawn

提示

t_1…t_N的长度和不超过10^5
所有字符串都只包含小写字母


显然建立AC自动机进行匹配,那么考虑删除操作,用一个栈来存已匹配过的主串,那么删除时直接退栈即可,问题在于退栈后应该从AC自动机的哪一个节点开始讨论,因此需要存下主串每一个位置匹配后在AC自动机上的位置,那么退栈时只要跑到之前存下的位置即可。

需要注意的是建立Fail指针的时候,某些写法会超时,背一个优秀的模板。


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define N 100005
using namespace std;
struct node{int Son[26],k,Fail,dep;};
node trie[N*10];
char S[N],s[N];
int C[N],D[N],Top;
int n,l,tot=1;
queue<int>Q;
void Ins()
{
    int i,p=1,le=strlen(s)-1;
    for(i=1;i<=le;i++)
    {
        int t=s[i]-'a';
        if(!trie[p].Son[t])trie[p].Son[t]=++tot,trie[tot].dep=trie[p].dep+1;
        p=trie[p].Son[t];
    }
    trie[p].k=1;
}
void BUF()
{
    int i,p,son,tmp;
    for(i=0;i<26;i++)
    {
        if(trie[1].Son[i])trie[trie[1].Son[i]].Fail=1,Q.push(trie[1].Son[i]);
        else trie[1].Son[i]=1;
    }
    while(Q.size())
    {
        p=Q.front();Q.pop();
        for(i=0;i<26;i++)
        if(trie[p].Son[i])trie[trie[p].Son[i]].Fail=trie[trie[p].Fail].Son[i],Q.push(trie[p].Son[i]);
        else trie[p].Son[i]=trie[trie[p].Fail].Son[i];
    }
}
void Find()
{
    int i=0,p=1,t;D[0]=1;
    while(++i<=l)
    {
        t=S[i]-'a';
        p=D[C[Top]];
        C[++Top]=i;
        while(p&&(!trie[p].Son[t]))p=trie[p].Fail;
        p=trie[p].Son[t];
        if(!p)p=1;
        if(trie[p].k==1)Top-=trie[p].dep;
        D[i]=p;
    }
}
int main()
{
    int i;
    scanf("%s",&S[1]);S[0]='%';
    l=strlen(S)-1;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        scanf("%s",&s[1]);s[0]='%';
        Ins();
    }
    BUF();Find();
    for(i=1;i<=Top;i++)printf("%c",S[C[i]]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值