Typing practice(KMP+数组优化 或 AC自动机+dp)

原题:牛客网暑期ACM多校训练营(第九场)F

题意:

有n个串(n<=4),和一个空串A,一个操作串.

操作串的每一个字符代表一个对A的操作

  1. 小写英文字母代表往A后面添加这个字母
  2. 字符’-‘代表删除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点为止的前缀的最短后缀长度(比如 a b c d d a b c d d d ,走到了 a b c ,那么显然要选 a b c d d )

其次,是temp的fail的最短后缀长度(比如 a b c d d d b c d d ,走到 a b c 的时候, a b c c 指向了 b c c ,明显 b c d d 会更优)

但是如果每次走到一个点的时候都遍历一边所有的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);
}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/81876161