题意:
有n个串(n<=4),和一个空串A,一个操作串.
操作串的每一个字符代表一个对A的操作
- 小写英文字母代表往A后面添加这个字母
- 字符’-‘代表删除A最后面的一个字符(如果没有就不操作)
求每次操作后,最少添加多少个(不是真的添加)字符使n个串中至少有一个串为A串的后缀
KMP做法:
因为模式串最多只有4个,所以我们其实不需要用到多串的匹配,直接对每个模式串做一遍KMP(当然,这个KMP是fail数组优化后的KMP),然后跑一下再对每个操作求一个最小值即可
#include<bits/stdc++.h>
using namespace std;
const int N=200009;
void KMP(char *x,int *fail){
int len=strlen(x);
int i=0,j=-1;
fail[0]=-1;
while(i<len){
while(j!=-1&&x[i]!=x[j]) j=fail[j];
fail[++i]=++j;
if(x[i]==x[j]) fail[i]=fail[j];
}
}
char x[5][N],a[N];int fail[N];
int ans[N];
int main(){
int n;cin>>n;
for(int i=1;i<=n;i++)scanf("%s",x[i]);
scanf("%s",a);
int len=strlen(a);
for(int i=0;i<=len;i++)ans[i]=1e9;
int mi=1e9;
for(int i=1;i<=n;i++){
int lens=strlen(x[i]);
mi=min(mi,lens);
KMP(x[i],fail);
stack<int>q;int now=0;
for(int j=0;j<len;j++){
if(a[j]=='-'){if(!q.empty())q.pop();if(!q.empty())now=q.top();else now=0;}
else{
while(x[i][now]!=a[j]&&now!=-1)now=fail[now];
q.push(++now);
}
ans[j]=min(ans[j],lens-now);
}
}
printf("%d\n",mi);
for(int i=0;i<len;i++)printf("%d\n",ans[i]);
}
AC自动机做法:
就算4个串KMP不优化还是会T,所以说字符串的问题还是得靠AC自动机
如果把多个串放到Trie图中,假设走到了点temp
首先我们要知道至temp点为止的前缀的最短后缀长度(比如 和 ,走到了 ,那么显然要选 )
其次,是temp的fail的最短后缀长度(比如 和 ,走到 的时候, 的 指向了 的 ,明显 会更优)
但是如果每次走到一个点的时候都遍历一边所有的fail,相当于重复走了很多路,肯定是会TLE的,故用dp数组来记录一个位置和所有fail的最短后缀的最小值
代码:
#include<bits/stdc++.h>
#define maxlen 100005
using namespace std;
struct Trie
{
int next[maxlen][30],fail[maxlen],end[maxlen],root,L,mi[maxlen],sta[maxlen],dp[maxlen];//next记录节点,在这里end指针代表以当前节点为字符串尾的字符串个数
int newnode()
{
for(int i=0;i<30;i++)
next[L][i]=-1;//节点连接的边初始化为-1
end[L]=0;
dp[L]=-1;
return L++;
}
void init()
{
L=0;
root=newnode();
}
void insert(char buf[])//trie树的建立
{
int l=strlen(buf);
int now=root;
for(int i=0;i<l;i++)
{
if(next[now][buf[i]-'a']==-1)next[now][buf[i]-'a']=newnode();
now=next[now][buf[i]-'a'];
}
end[now]++;
}
void build()//建立ac自动机
{
queue<int>que;
for(int i=0;i<30;i++)
{
if(next[root][i]==-1)next[root][i]=root;
else //若有连边则将节点加入队列 ,并将fail指针指向root
{
fail[next[root][i]]=root;
que.push(next[root][i]);
}
}
while(!que.empty())
{
int now=que.front();
que.pop();
for(int i=0;i<30;i++)
{
if(next[now][i]==-1) //若无连边,则将该边指向当前节点fail指针指向的相应字符连接的节点
next[now][i]=next[fail[now]][i];
else //若有连边,则将儿子节点的fail指针指向当前节点fail指针指向相应字符接的节点
{
fail[next[now][i]]=next[fail[now]][i];
que.push(next[now][i]); //加入队列继续遍历
}
}
}
}
int GG(int temp)
{
if(temp==root)return mi[temp];
if(dp[temp]==-1)return dp[temp]=min(mi[temp],GG(fail[temp]));
return dp[temp];
}
void query(char buf[])
{
int l=strlen(buf);
int now=root,top=0;
printf("%d\n",mi[now]);
sta[top++]=now;
for(int i=0;i<l;i++)
{
if(buf[i]=='-'&&top>1)now=sta[top-2],top--;
else
{
now=next[now][buf[i]-'a'];
sta[top++]=now;
}
int ans=min(mi[now],GG(now));//比较自己的最短后缀和fails的最短后缀
printf("%d\n",ans);
}
}
void dfs(int root)
{//得出每个点的最短后缀
mi[root]=maxlen;
int flag=0;
for(int i=0;i<30;i++)
{
if(next[root][i]==-1)continue;
flag=1;
dfs(next[root][i]);
mi[root]=min(mi[root],mi[next[root][i]]+1);//得出这部分前缀的最短后缀长度
}
if(!flag)mi[root]=0;//无后缀
}
}ac;
char buf[maxlen];
int main()
{
ac.init();
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",buf);
ac.insert(buf);
}
ac.dfs(ac.root);
ac.build();
scanf("%s",buf);
ac.query(buf);
}