题目描述:
FJ把杂志上所有的文章摘抄了下来并把它变成了一个长度不超过10^5的字符串S。他有一个包含n个单词的列表,列表里的n个单词记为t_1...t_N。他希望从S中删除这些单词。FJ每次在S中找到最早出现的列表中的单词(最早出现指该单词的开始位置最小),然后从S中删除这个单词。他重复这个操作直到S中没有列表里的单词为止。注意删除一个单词后可能会导致S中出现另一个列表中的单词FJ注意到列表中的单词不会出现一个单词是另一个单词子串的情况,这意味着每个列表中的单词在S中出现的开始位置是互不相同的请帮助FJ完成这些操作并输出最后的S
输入样例#1:
begintheescapexecutionatthebreakofdawn
2
escape
execution
输出样例#1:
beginthatthebreakofdawn
题解:AC自动机+栈。首先毫无疑问肯定是AC自动机去匹配串,删除操作可以通过栈来实现,对于模式串,当前这一位不是一个单词的结尾的话,就压入栈中,继续AC自动机过程,若当前这一位是一个单词的结尾,说明匹配到一个串,于是需要将栈中当前的单词所有弹出,所以需要单词长度,因此在建立树的时候在单词末尾节点维护一个长度信息,直接top-=len[i],继续匹配。(很气的是明明只要100+的时间居然跑了600+。。。。无语了)
总结:字符串的题一定要弄清楚过程!过程很重要!过程很重要!过程很重要!不管是kmp,AC自动机还是后缀数组,后缀自动机,只要将题目的关键条件往过程上靠,就一定没有问题。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#define N 100005
using namespace std;
queue<int> q;
char ptr[N],str[N];
int lost[N];
char BB[N];
int n;
int fail[N],date[500000][26],stack[N],top=0,tot=0,cnt[N],len[N];
int pt,st;
void build()
{
for(int i=0;i<26;i++)
if(date[0][i])
q.push(date[0][i]);
while(!q.empty())
{
int re=q.front();q.pop();
for(int i=0;i<26;i++)
{
if(!date[re][i])
{
date[re][i]=date[fail[re]][i];
continue;
}
q.push(date[re][i]);
fail[date[re][i]]=date[fail[re]][i];
}
}
}
int main()
{
//freopen("sss.in","r",stdin);
// freopen("阿娇的方式.out","w",stdout);
memset(date,0,sizeof(date));
scanf("%s",ptr+1);pt=strlen(ptr+1);
scanf("%d",&n);
while(n--)
{
scanf("%s",str+1);st=strlen(str+1);
int j=0;
for(int i=1;i<=st;i++)
{
if(!date[j][str[i]-'a']) date[j][str[i]-'a']=++tot,BB[tot]=str[i];
j=date[j][str[i]-'a'];
}
cnt[j]++;len[j]=st;
}
// for(int i=1;i<=tot;i++) cout<<i<<" RANH "<<BB[i]<<endl;
build();
int j=0;
for(int i=1;i<=pt;i++)
{
stack[++top]=i;
j=date[j][ptr[i]-'a'];
if(cnt[j])
{
int tmp=len[j];
top-=len[j];
j=lost[stack[top]];
}else lost[i]=j;
//cout<<top<<" "<<j<<endl;
}
for(int i=1;i<=top;i++) cout<<ptr[stack[i]];
return 0;
}